2005-05-11 Wim Taymans <wim@fluendo.com>
+ * gst/rtsp/README:
+ * gst/rtsp/gstrtspsrc.c: (gst_rtsp_proto_get_type),
+ (gst_rtspsrc_class_init), (gst_rtspsrc_create_stream),
+ (gst_rtspsrc_add_element), (gst_rtspsrc_set_state),
+ (gst_rtspsrc_stream_setup_rtp),
+ (gst_rtspsrc_stream_configure_transport), (find_stream),
+ (gst_rtspsrc_loop), (gst_rtspsrc_open), (gst_rtspsrc_play):
+ * gst/rtsp/rtsp.h:
+ * gst/rtsp/rtspconnection.c: (rtsp_connection_create),
+ (rtsp_connection_send), (read_line), (parse_request_line),
+ (parse_line), (read_body), (rtsp_connection_receive),
+ (rtsp_connection_free):
+ * gst/rtsp/rtspconnection.h:
+ * gst/rtsp/rtspdefs.c: (rtsp_find_method):
+ * gst/rtsp/rtspdefs.h:
+ * gst/rtsp/rtspmessage.c: (rtsp_message_set_body),
+ (rtsp_message_take_body):
+ * gst/rtsp/rtspmessage.h:
+ * gst/rtsp/rtspstream.h:
+ * gst/rtsp/sdpmessage.c: (sdp_parse_line):
+ Added README
+ Some cleanups.
+
+2005-05-11 Wim Taymans <wim@fluendo.com>
+
* gst/rtsp/gstrtspsrc.c: (gst_rtsp_proto_get_type),
(gst_rtspsrc_class_init), (gst_rtspsrc_init),
(gst_rtspsrc_create_stream), (gst_rtspsrc_add_element),
--- /dev/null
+RTSP source
+-----------
+
+The RTSP source establishes a connection to an RTSP server and sets up
+the UDP sources and RTP session handlers.
+
+An RTSP session is created as follows:
+
+- Parse RTSP URL:
+
+ ex:
+ rtsp://thread:5454/south-rtsp.mp3
+
+- Open a connection to the server with the url. All further conversation with
+ the server should be done with this connection. Each request/reply has
+ a CSeq number added to the header.
+
+- Send a DESCRIBE request for the url. We currently support a response in
+ SDP.
+
+ ex:
+
+ >> DESCRIBE rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0
+ >> Accept: application/sdp
+ >> CSeq: 0
+ >>
+ << RTSP/1.0 200 OK
+ << Content-Length: 84
+ << Content-Type: application/sdp
+ << CSeq: 0
+ << Date: Wed May 11 13:09:37 2005 GMT
+ <<
+ << v=0
+ << o=- 0 0 IN IP4 192.168.1.1
+ << s=No Title
+ << m=audio 0 RTP/AVP 14
+ << a=control:streamid=0
+
+- Parse the SDP message, for each stream (m=) we create an GstRTPStream. We need
+ to allocate two local UDP ports for receiving the RTP and RTCP data because we
+ need to send the port numbers to the server in the next request.
+
+ In RTSPSrc we create two elements that can handle the udp://0.0.0.0:0 uri. This
+ will create an udp source element. We get the port number by getting the "port"
+ property of the element after setting the element to PAUSED.
+
+ +-----------------+
+ | +------------+ |
+ | | udpsrc0 | |
+ | | port=5000 | |
+ | +------------+ |
+ | +------------+ |
+ | | udpsrc1 | |
+ | | port=5001 | |
+ | +------------+ |
+ +-----------------+
+
+- Send a SETUP message to the server with the RTP ports. We get the setup URI from
+ the a= attribute in the SDP message. This can be an absolute URL or a relative
+ url.
+
+ ex:
+
+ >> SETUP rtsp://thread:5454/south-rtsp.mp3/streamid=0 RTSP/1.0
+ >> CSeq: 1
+ >> Transport: RTP/AVP/UDP;unicast;client_port=5000-5001,RTP/AVP/UDP;multicast,RTP/AVP/TCP
+ >>
+ << RTSP/1.0 200 OK
+ << Transport: RTP/AVP/UDP;unicast;client_port=5000-5001;server_port=6000-6001
+ << CSeq: 1
+ << Date: Wed May 11 13:21:43 2005 GMT
+ << Session: 5d5cb94413288ccd
+ <<
+
+ The client needs to send the local ports to the server along with the supported
+ transport types. The server selects the final transport which it returns in the
+ Transport header field. The server also includes its ports where RTP and RTCP
+ messages can be sent to.
+
+ In the above example UDP was choosen as a transport. At this point the RTSPSrc element
+ will furter configure its elements to process this stream.
+
+ The RTSPSrc will create and connect an RTP session manager element and will
+ connect it to the src pads of the udp element. The data pad from the RTP session
+ manager is ghostpadded to RTPSrc.
+ The RTCP pad of the rtpdec is routed to a new udpsink that sends data to the RTCP
+ port of the server as returned in the Transport: header field.
+
+ +---------------------------------------------+
+ | +------------+ |
+ | | udpsrc0 | +--------+ |
+ | | port=5000 ----- rtpdec --------------------
+ | +------------+ | | |
+ | +------------+ | | +------------+ |
+ | | udpsrc1 ----- RTCP ---- udpsink | |
+ | | port=5001 | +--------+ | port=6001 | |
+ | +------------+ +------------+ |
+ +---------------------------------------------+
+
+ The output type of rtpdec is configured as the media type specified in the SDP
+ message.
+
+- All the elements are set to PAUSED/PLAYING and the PLAY RTSP message is
+ sent.
+
+ >> PLAY rtsp://thread:5454/south-rtsp.mp3 RTSP/1.0
+ >> CSeq: 2
+ >>
+ << RTSP/1.0 200 OK
+ << CSeq: 2
+ << Date: Wed May 11 13:21:43 2005 GMT
+ << Session: 5d5cb94413288ccd
+ <<
+
+- The udp source elements receive data from that point and the RTP/RTCP messages
+ are processed by the elements.
+
+- In the case of interleaved mode, the SETUP method yields:
+
+ >> SETUP rtsp://thread:5454/south-rtsp.mp3/streamid=0 RTSP/1.0
+ >> CSeq: 1
+ >> Transport: RTP/AVP/UDP;unicast;client_port=5000-5001,RTP/AVP/UDP;multicast,RTP/AVP/TCP
+ >>
+ << RTSP/1.0 200 OK
+ << Transport: RTP/AVP/TCP;interleaved=0-1
+ << CSeq: 1
+ << Date: Wed May 11 13:21:43 2005 GMT
+ << Session: 5d5cb94413288ccd
+ <<
+
+ This means that RTP/RTCP messages will be send on channel 0/1 respectively and that
+ the data will be received on the same connection as the RTSP connection.
+
+ At this point, we remove the UDP source elements as we don't need them anymore. We
+ setup the rtpdec session manager element though as follows:
+
+ +---------------------------------------------+
+ | +------------+ |
+ | | _loop() | +--------+ |
+ | | ----- rtpdec --------------------
+ | | | | | |
+ | | | | | +------------+ |
+ | | ----- RTCP ---- udpsink | |
+ | | | +--------+ | port=6001 | |
+ | +------------+ +------------+ |
+ +---------------------------------------------+
+
+ We start an interal task to start reading from the RTSP connection waiting
+ for data. The received data is then pushed to the rtpdec element.
+
+ When reading from the RTSP connection we receive data packets in the
+ following layout (see also RFC2326)
+
+ $<1 byte channel><2 bytes length, big endian><length bytes of data>
+
+
new =
g_strconcat (transports, transports[0] ? "," : "",
- ",RTP/AVP/UDP;multicast", NULL);
+ "RTP/AVP/UDP;multicast", NULL);
g_free (transports);
transports = new;
}
gchar *new;
new =
- g_strconcat (transports, transports[0] ? "," : "", ",RTP/AVP/TCP",
+ g_strconcat (transports, transports[0] ? "," : "", "RTP/AVP/TCP",
NULL);
g_free (transports);
transports = new;
#include <rtspconnection.h>
#include <rtspdefs.h>
#include <rtspmessage.h>
-#include <rtspstream.h>
#include <rtsptransport.h>
#include <rtspurl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
+#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
{
RTSPConnection *newconn;
- /* FIXME check fd */
+ /* FIXME check fd, must be connected SOCK_STREAM */
newconn = g_new (RTSPConnection, 1);
rtsp_connection_send (RTSPConnection * conn, RTSPMessage * message)
{
GString *str;
+ gint towrite;
+ gchar *data;
if (conn == NULL || message == NULL)
return RTSP_EINVAL;
str = g_string_new ("");
+ /* create request string, add CSeq */
g_string_append_printf (str, "%s %s RTSP/1.0\r\n"
"CSeq: %d\r\n",
rtsp_method_as_text (message->type_data.request.method),
message->type_data.request.uri, conn->cseq);
+ /* append session id if we have one */
if (conn->session_id[0] != '\0') {
rtsp_message_add_header (message, RTSP_HDR_SESSION, conn->session_id);
}
+ /* append headers */
g_hash_table_foreach (message->hdr_fields, (GHFunc) append_header, str);
+ /* append Content-Length and body if needed */
+ if (message->body != NULL && message->body_size > 0) {
+ gchar *len;
- g_string_append (str, "\r\n");
+ len = g_strdup_printf ("%d", message->body_size);
+ append_header (RTSP_HDR_CONTENT_LENGTH, len, str);
+ g_free (len);
+ /* header ends here */
+ g_string_append (str, "\r\n");
+ str = g_string_append_len (str, message->body, message->body_size);
+ } else {
+ /* just end headers */
+ g_string_append (str, "\r\n");
+ }
+
+ /* write request */
+ towrite = str->len;
+ data = str->str;
- write (conn->fd, str->str, str->len);
+ while (towrite > 0) {
+ gint written;
+
+ written = write (conn->fd, data, towrite);
+ if (written < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ goto write_error;
+ } else {
+ towrite -= written;
+ data += written;
+ }
+ }
g_string_free (str, TRUE);
+ conn->cseq++;
+
return RTSP_OK;
+
+write_error:
+ {
+ g_string_free (str, TRUE);
+ return RTSP_ESYS;
+ }
}
static RTSPResult
{
gint idx;
gchar c;
- gint ret;
+ gint r;
idx = 0;
while (TRUE) {
- ret = read (fd, &c, 1);
- if (ret < 1)
- goto error;
-
- if (c == '\n') /* end on \n */
- break;
- if (c == '\r') /* ignore \r */
- continue;
+ r = read (fd, &c, 1);
+ if (r < 1) {
+ if (errno != EAGAIN && errno != EINTR)
+ goto read_error;
+ } else {
+ if (c == '\n') /* end on \n */
+ break;
+ if (c == '\r') /* ignore \r */
+ continue;
- if (idx < size - 1)
- buffer[idx++] = c;
+ if (idx < size - 1)
+ buffer[idx++] = c;
+ }
}
buffer[idx] = '\0';
return RTSP_OK;
-error:
+read_error:
{
- perror ("read");
return RTSP_ESYS;
}
}
}
static RTSPResult
+parse_request_line (gchar * buffer, RTSPMessage * msg)
+{
+ gchar versionstr[20];
+ gchar methodstr[20];
+ gchar urlstr[4096];
+ gchar *bptr;
+ RTSPMethod method;
+
+ bptr = buffer;
+
+ read_string (methodstr, sizeof (methodstr), &bptr);
+ method = rtsp_find_method (methodstr);
+ if (method == -1)
+ goto wrong_method;
+
+ read_string (urlstr, sizeof (urlstr), &bptr);
+
+ read_string (versionstr, sizeof (versionstr), &bptr);
+ if (strcmp (versionstr, "RTSP/1.0") != 0)
+ goto wrong_version;
+
+ rtsp_message_init_request (method, urlstr, msg);
+
+ return RTSP_OK;
+
+wrong_method:
+ {
+ return RTSP_EINVAL;
+ }
+wrong_version:
+ {
+ return RTSP_EINVAL;
+ }
+}
+
+/* parsing lines means reading a Key: Value pair */
+static RTSPResult
parse_line (gchar * buffer, RTSPMessage * msg)
{
gchar key[32];
bptr = buffer;
+ /* read key */
read_key (key, sizeof (key), &bptr);
if (*bptr != ':')
return RTSP_EINVAL;
field = rtsp_find_header_field (key);
if (field == -1) {
- g_warning ("unknown header field '%s'\n", key);
+ g_warning ("ignoring unknown header field '%s'\n", key);
} else {
while (g_ascii_isspace (*bptr))
bptr++;
gint to_read, r;
if (content_length <= 0) {
- rtsp_message_set_body (msg, NULL, 0);
- return RTSP_OK;
+ body = NULL;
+ content_length = 0;
+ goto done;
}
body = g_malloc (content_length);
to_read = content_length;
while (to_read > 0) {
r = read (fd, bodyptr, to_read);
-
- to_read -= r;
- bodyptr += r;
+ if (r < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ goto read_error;
+ } else {
+ to_read -= r;
+ bodyptr += r;
+ }
}
+done:
rtsp_message_set_body (msg, body, content_length);
return RTSP_OK;
+
+read_error:
+ {
+ g_free (body);
+ return RTSP_ESYS;
+ }
}
RTSPResult
need_body = TRUE;
+ res = RTSP_OK;
/* parse first line and headers */
- while (TRUE) {
+ while (res == RTSP_OK) {
gchar c;
gint ret;
+ /* read first character, this identifies data messages */
ret = read (conn->fd, &c, 1);
if (ret < 0)
goto read_error;
if (ret < 1)
break;
- /* check for data packet */
+ /* check for data packet, first character is $ */
if (c == '$') {
guint16 size;
- /* read channel */
+ /* data packets are $<1 byte channel><2 bytes length,BE><data bytes> */
+
+ /* read channel, which is the next char */
ret = read (conn->fd, &c, 1);
if (ret < 0)
goto read_error;
if (ret < 1)
goto error;
+ /* now we create a data message */
rtsp_message_init_data ((gint) c, msg);
+ /* next two bytes are the length of the data */
ret = read (conn->fd, &size, 2);
if (ret < 0)
goto read_error;
size = GUINT16_FROM_BE (size);
- read_body (conn->fd, size, msg);
+ /* and read the body */
+ res = read_body (conn->fd, size, msg);
need_body = FALSE;
break;
} else {
gint offset = 0;
+ /* we have a regular response */
if (c != '\r') {
buffer[0] = c;
offset = 1;
if (c == '\n')
break;
- read_line (conn->fd, buffer + offset, sizeof (buffer) - offset);
+ /* read lines */
+ res = read_line (conn->fd, buffer + offset, sizeof (buffer) - offset);
+ if (res != RTSP_OK)
+ goto read_error;
if (buffer[0] == '\0')
break;
if (line == 0) {
+ /* first line, check for response status */
if (g_str_has_prefix (buffer, "RTSP")) {
- parse_response_status (buffer, msg);
+ res = parse_response_status (buffer, msg);
} else {
- g_warning ("parsing request not implemented\n");
- goto error;
+ res = parse_request_line (buffer, msg);
}
} else {
+ /* else just parse the line */
parse_line (buffer, msg);
}
}
line++;
}
+ /* read the rest of the body if needed */
if (need_body) {
- /* parse body */
- res = rtsp_message_get_header (msg, RTSP_HDR_CONTENT_LENGTH, &hdrval);
- if (res == RTSP_OK) {
+ /* see if there is a Content-Length header */
+ if (rtsp_message_get_header (msg, RTSP_HDR_CONTENT_LENGTH,
+ &hdrval) == RTSP_OK) {
+ /* there is, read the body */
content_length = atol (hdrval);
- read_body (conn->fd, content_length, msg);
+ res = read_body (conn->fd, content_length, msg);
}
- /* save session id */
+ /* save session id in the connection for further use */
{
gchar *session_id;
if (rtsp_message_get_header (msg, RTSP_HDR_SESSION,
&session_id) == RTSP_OK) {
- strncpy (conn->session_id, session_id, sizeof (conn->session_id) - 1);
- conn->session_id[sizeof (conn->session_id) - 1] = '\0';
+ gint maxlen = sizeof (conn->session_id) - 1;
+
+ /* make sure to not overflow */
+ strncpy (conn->session_id, session_id, maxlen);
+ conn->session_id[maxlen] = '\0';
}
}
}
-
- return RTSP_OK;
+ return res;
error:
{
}
read_error:
{
- perror ("read");
return RTSP_ESYS;
}
}
return RTSP_ESYS;
}
}
+
+RTSPResult
+rtsp_connection_free (RTSPConnection * conn)
+{
+ if (conn == NULL)
+ return RTSP_EINVAL;
+
+ g_free (conn);
+}
#include <rtspdefs.h>
#include <rtspurl.h>
-#include <rtspstream.h>
#include <rtspmessage.h>
G_BEGIN_DECLS
typedef struct _RTSPConnection
{
- gint fd;
+ gint fd; /* our socket */
- gint cseq;
- gchar session_id[512];
-
- RTSPState state;
-
- int num_streams;
- RTSPStream **streams;
+ gint cseq; /* sequence number */
+ gchar session_id[512]; /* session id */
+ RTSPState state; /* state of the connection */
} RTSPConnection;
+/* opening/closing a connection */
RTSPResult rtsp_connection_open (RTSPUrl *url, RTSPConnection **conn);
RTSPResult rtsp_connection_create (gint fd, RTSPConnection **conn);
+RTSPResult rtsp_connection_close (RTSPConnection *conn);
+RTSPResult rtsp_connection_free (RTSPConnection *conn);
+/* sending/receiving messages */
RTSPResult rtsp_connection_send (RTSPConnection *conn, RTSPMessage *message);
RTSPResult rtsp_connection_receive (RTSPConnection *conn, RTSPMessage *message);
-RTSPResult rtsp_connection_close (RTSPConnection *conn);
-
G_END_DECLS
#endif /* __RTSP_CONNECTION_H__ */
}
return -1;
}
+
+RTSPMethod
+rtsp_find_method (gchar * method)
+{
+ gint idx;
+
+ for (idx = 0; rtsp_methods[idx]; idx++) {
+ if (g_ascii_strcasecmp (rtsp_headers[idx], method) == 0) {
+ return idx;
+ }
+ }
+ return -1;
+}
const gchar* rtsp_header_as_text (RTSPHeaderField field);
const gchar* rtsp_status_as_text (RTSPStatusCode code);
const gchar* rtsp_status_to_string (RTSPStatusCode code);
+
RTSPHeaderField rtsp_find_header_field (gchar *header);
+RTSPMethod rtsp_find_method (gchar *method);
G_END_DECLS
}
RTSPResult
-rtsp_message_get_header_copy (RTSPMessage * msg, RTSPHeaderField field,
- gchar ** value)
+rtsp_message_set_body (RTSPMessage * msg, guint8 * data, guint size)
{
- gchar *val;
-
- if (msg == NULL || value == NULL)
+ if (msg == NULL)
return RTSP_EINVAL;
- val = g_hash_table_lookup (msg->hdr_fields, GINT_TO_POINTER (field));
-
- *value = g_strdup (val);
-
- return RTSP_OK;
+ return rtsp_message_take_body (msg, g_memdup (data, size), size);
}
-
RTSPResult
-rtsp_message_set_body (RTSPMessage * msg, guint8 * data, guint size)
+rtsp_message_take_body (RTSPMessage * msg, guint8 * data, guint size)
{
if (msg == NULL)
return RTSP_EINVAL;
}
RTSPResult
-rtsp_message_set_body_copy (RTSPMessage * msg, guint8 * data, guint size)
-{
- return RTSP_ENOTIMPL;
-}
-
-
-RTSPResult
rtsp_message_get_body (RTSPMessage * msg, guint8 ** data, guint * size)
{
if (msg == NULL || data == NULL || size == NULL)
return RTSP_OK;
}
-RTSPResult
-rtsp_message_get_body_copy (RTSPMessage * msg, guint8 ** data, guint * size)
-{
- if (msg == NULL || data == NULL || size == NULL)
- return RTSP_EINVAL;
-
- *data = g_memdup (msg->body, msg->body_size);
- *size = msg->body_size;
-
- return RTSP_OK;
-}
-
static void
dump_mem (guint8 * mem, gint size)
{
RTSPResult rtsp_message_add_header (RTSPMessage *msg, RTSPHeaderField field, gchar *value);
RTSPResult rtsp_message_remove_header (RTSPMessage *msg, RTSPHeaderField field);
RTSPResult rtsp_message_get_header (RTSPMessage *msg, RTSPHeaderField field, gchar **value);
-RTSPResult rtsp_message_get_header_copy (RTSPMessage *msg, RTSPHeaderField field, gchar **value);
RTSPResult rtsp_message_set_body (RTSPMessage *msg, guint8 *data, guint size);
-RTSPResult rtsp_message_set_body_copy (RTSPMessage *msg, guint8 *data, guint size);
-
+RTSPResult rtsp_message_take_body (RTSPMessage *msg, guint8 *data, guint size);
RTSPResult rtsp_message_get_body (RTSPMessage *msg, guint8 **data, guint *size);
-RTSPResult rtsp_message_get_body_copy (RTSPMessage *msg, guint8 **data, guint *size);
RTSPResult rtsp_message_dump (RTSPMessage *msg);
+++ /dev/null
-/* GStreamer
- * Copyright (C) <2005> Wim Taymans <wim@fluendo.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#ifndef __RTSP_STREAM_H__
-#define __RTSP_STREAM_H__
-
-#include <rtspdefs.h>
-
-G_BEGIN_DECLS
-
-typedef struct _RTSPStream {
-} RTSPStream;
-
-G_END_DECLS
-
-#endif /* __RTSP_STREAM_H__ */
switch (type) {
case 'v':
if (buffer[0] != '0')
- g_print ("wrong SDP version\n");
+ g_warning ("wrong SDP version");
sdp_message_set_version (c->msg, buffer);
break;
case 'o':
sdp_media_add_format (&nmedia, str);
} while (*p != '\0');
- g_print ("%p\n", &nmedia);
sdp_message_add_media (c->msg, &nmedia);
c->media =
&g_array_index (c->msg->medias, SDPMedia, c->msg->medias->len - 1);
- g_print ("%p\n", c->media);
break;
}
default: