1 /*****************************************************************************
2 * rtsp.c: minimalistic implementation of rtsp protocol.
3 * Not RFC 2326 compilant yet and only handle REAL RTSP.
4 *****************************************************************************
5 * Copyright (C) 2002-2004 the xine project
6 * Copyright (C) 2005 VideoLAN
9 * Authors: Gildas Bazin <gbazin@videolan.org>
10 * Adapted from xine which itself adapted it from joschkas real tools.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_access.h>
33 #include <vlc_messages.h>
38 #define HEADER_SIZE 1024
39 #define MAX_FIELDS 256
52 unsigned int server_state
;
58 char *answers
[MAX_FIELDS
]; /* data of last message */
59 char *scheduled
[MAX_FIELDS
]; /* will be sent with next message */
66 const char rtsp_protocol_version
[]="RTSP/1.0";
69 #define RTSP_CONNECTED 1
72 #define RTSP_PLAYING 8
73 #define RTSP_RECORDING 16
75 /* server capabilities */
76 #define RTSP_OPTIONS 0x001
77 #define RTSP_DESCRIBE 0x002
78 #define RTSP_ANNOUNCE 0x004
79 #define RTSP_SETUP 0x008
80 #define RTSP_GET_PARAMETER 0x010
81 #define RTSP_SET_PARAMETER 0x020
82 #define RTSP_TEARDOWN 0x040
83 #define RTSP_PLAY 0x080
84 #define RTSP_RECORD 0x100
87 * rtsp_get gets a line from stream
88 * and returns a null terminated string (must be freed).
91 static char *rtsp_get( rtsp_client_t
*rtsp
)
93 char *psz_buffer
= xmalloc( BUF_SIZE
);
94 char *psz_string
= NULL
;
96 if( rtsp
->pf_read_line( rtsp
->p_userdata
, (uint8_t*)psz_buffer
, (unsigned int)BUF_SIZE
) >= 0 )
98 //printf( "<< '%s'\n", psz_buffer );
99 psz_string
= strdup( psz_buffer
);
108 * rtsp_put puts a line on stream
111 static int rtsp_put( rtsp_client_t
*rtsp
, const char *psz_string
)
113 unsigned int i_buffer
= strlen( psz_string
);
114 uint8_t *psz_buffer
= xmalloc( i_buffer
+ 2 );
117 memcpy( psz_buffer
, psz_string
, i_buffer
);
118 psz_buffer
[i_buffer
] = '\r'; psz_buffer
[i_buffer
+1] = '\n';
120 i_ret
= rtsp
->pf_write( rtsp
->p_userdata
, psz_buffer
, i_buffer
+ 2 );
127 * extract server status code
130 static int rtsp_get_status_code( rtsp_client_t
*rtsp
, const char *psz_string
)
133 char psz_buffer
[4] = {0,0,0,0};
136 if( !strncmp( psz_string
, "RTSP/1.0", sizeof("RTSP/1.0") - 1 ) )
138 strncpy(psz_buffer
, psz_string
+ sizeof("RTSP/1.0"), 3);
139 i_code
= atoi( psz_buffer
);
141 else if( !strncmp( psz_string
, "SET_PARAMETER", sizeof("SET_PARAMETER") - 1 ) )
143 return RTSP_STATUS_SET_PARAMETER
;
148 //fprintf( stderr, "librtsp: server responds: '%s'\n", psz_string );
158 static int rtsp_send_request( rtsp_client_t
*rtsp
, const char *psz_type
,
159 const char *psz_what
)
165 if (rtsp
->p_private
== NULL
)
168 ppsz_payload
= rtsp
->p_private
->scheduled
;
170 psz_buffer
= xmalloc( strlen(psz_type
) + strlen(psz_what
) +
171 sizeof("RTSP/1.0") + 2 );
173 sprintf( psz_buffer
, "%s %s %s", psz_type
, psz_what
, "RTSP/1.0" );
174 i_ret
= rtsp_put( rtsp
, psz_buffer
);
177 for (i
= 0; i
< MAX_FIELDS
; ++i
) {
178 if (!ppsz_payload
[i
])
181 rtsp_put (rtsp
, ppsz_payload
[i
]);
184 rtsp_put( rtsp
, "" );
185 rtsp_unschedule_all( rtsp
);
191 * schedule standard fields
194 static void rtsp_schedule_standard( rtsp_client_t
*rtsp
)
196 char tmp
[sizeof("CSeq: ") + 3 * sizeof(int)];
198 sprintf( tmp
, "CSeq: %u", rtsp
->p_private
->cseq
);
199 rtsp_schedule_field( rtsp
, tmp
);
201 if( rtsp
->p_private
->session
)
204 buf
= xmalloc( strlen(rtsp
->p_private
->session
) + 15 );
205 sprintf( buf
, "Session: %s", rtsp
->p_private
->session
);
206 rtsp_schedule_field( rtsp
, buf
);
212 * get the answers, if server responses with something != 200, return NULL
215 static int rtsp_get_answers( rtsp_client_t
*rtsp
)
217 access_t
*p_access
= (access_t
*)rtsp
->p_userdata
;
219 unsigned int answer_seq
;
220 char **answer_ptr
= rtsp
->p_private
->answers
;
224 answer
= rtsp_get( rtsp
);
225 if( !answer
) return 0;
226 code
= rtsp_get_status_code( rtsp
, answer
);
229 rtsp_free_answers( rtsp
);
231 do { /* while we get answer lines */
233 answer
= rtsp_get( rtsp
);
234 if( !answer
) return 0;
236 if( !strncasecmp( answer
, "CSeq:", 5 ) )
238 if (sscanf( answer
, "%*s %u", &answer_seq
) == 1) {
239 if( rtsp
->p_private
->cseq
!= answer_seq
)
241 msg_Warn (p_access
, "Cseq mismatch, got %u, assumed %u", answer_seq
, rtsp
->p_private
->cseq
);
242 rtsp
->p_private
->cseq
= answer_seq
;
245 msg_Warn (p_access
, "remote server sent CSeq without payload, ignoring.");
248 if( !strncasecmp( answer
, "Server:", 7 ) )
250 char *buf
= xmalloc( strlen(answer
) );
251 if (sscanf( answer
, "%*s %s", buf
) == 1) {
252 free( rtsp
->p_private
->server
);
253 rtsp
->p_private
->server
= buf
;
255 msg_Warn(p_access
, "remote server sent Server without payload, ignoring.");
258 if( !strncasecmp( answer
, "Session:", 8 ) )
260 char *buf
= xmalloc( strlen(answer
) );
261 if (sscanf( answer
, "%*s %s", buf
) == 1) { // TODO: ignore attributes "Session: ${session-id};${attribute=value...}"
262 if( rtsp
->p_private
->session
)
264 if( strcmp( buf
, rtsp
->p_private
->session
) )
266 msg_Warn (p_access
, "setting NEW session: %s", buf
);
267 free( rtsp
->p_private
->session
);
268 rtsp
->p_private
->session
= strdup( buf
);
273 msg_Dbg (p_access
, "session id: '%s'", buf
);
274 rtsp
->p_private
->session
= strdup( buf
);
277 msg_Warn(p_access
, "remote server sent Session without payload, ignoring.");
282 *answer_ptr
= answer
;
284 } while( (strlen(answer
) != 0) && (++ans_count
< MAX_FIELDS
) );
286 rtsp
->p_private
->cseq
++;
288 if (ans_count
!= MAX_FIELDS
) {
292 rtsp_schedule_standard( rtsp
);
301 int rtsp_send_ok( rtsp_client_t
*rtsp
)
303 char cseq
[sizeof("CSeq: ") + 3 * sizeof(int)];
305 rtsp_put( rtsp
, "RTSP/1.0 200 OK" );
306 sprintf( cseq
, "CSeq: %u", rtsp
->p_private
->cseq
);
307 rtsp_put( rtsp
, cseq
);
308 rtsp_put( rtsp
, "" );
313 * implementation of must-have rtsp requests; functions return
314 * server status code.
317 int rtsp_request_options( rtsp_client_t
*rtsp
, const char *what
)
321 if( what
) buf
= strdup(what
);
324 buf
= xmalloc( strlen(rtsp
->p_private
->host
) + 16 );
325 sprintf( buf
, "rtsp://%s:%i", rtsp
->p_private
->host
,
326 rtsp
->p_private
->port
);
328 rtsp_send_request( rtsp
, "OPTIONS", buf
);
331 return rtsp_get_answers( rtsp
);
334 int rtsp_request_describe( rtsp_client_t
*rtsp
, const char *what
)
344 buf
= xmalloc( strlen(rtsp
->p_private
->host
) +
345 strlen(rtsp
->p_private
->path
) + 16 );
346 sprintf( buf
, "rtsp://%s:%i/%s", rtsp
->p_private
->host
,
347 rtsp
->p_private
->port
, rtsp
->p_private
->path
);
349 rtsp_send_request( rtsp
, "DESCRIBE", buf
);
352 return rtsp_get_answers( rtsp
);
355 int rtsp_request_setup( rtsp_client_t
*rtsp
, const char *what
)
357 rtsp_send_request( rtsp
, "SETUP", what
);
358 return rtsp_get_answers( rtsp
);
361 int rtsp_request_setparameter( rtsp_client_t
*rtsp
, const char *what
)
371 buf
= xmalloc( strlen(rtsp
->p_private
->host
) +
372 strlen(rtsp
->p_private
->path
) + 16 );
373 sprintf( buf
, "rtsp://%s:%i/%s", rtsp
->p_private
->host
,
374 rtsp
->p_private
->port
, rtsp
->p_private
->path
);
377 rtsp_send_request( rtsp
, "SET_PARAMETER", buf
);
380 return rtsp_get_answers( rtsp
);
383 int rtsp_request_play( rtsp_client_t
*rtsp
, const char *what
)
389 buf
= strdup( what
);
393 buf
= xmalloc( strlen(rtsp
->p_private
->host
) +
394 strlen(rtsp
->p_private
->path
) + 16 );
395 sprintf( buf
, "rtsp://%s:%i/%s", rtsp
->p_private
->host
,
396 rtsp
->p_private
->port
, rtsp
->p_private
->path
);
399 rtsp_send_request( rtsp
, "PLAY", buf
);
402 return rtsp_get_answers( rtsp
);
405 int rtsp_request_tearoff( rtsp_client_t
*rtsp
, const char *what
)
407 rtsp_send_request( rtsp
, "TEAROFF", what
);
408 return rtsp_get_answers( rtsp
);
412 * read opaque data from stream
415 int rtsp_read_data( rtsp_client_t
*rtsp
, uint8_t *buffer
, unsigned int size
)
421 i
= rtsp
->pf_read( rtsp
->p_userdata
, (uint8_t*)buffer
, (unsigned int) 4 );
422 if( i
< 4 ) return i
;
424 if( buffer
[0]=='S' && buffer
[1]=='E' && buffer
[2]=='T' &&
427 char *rest
= rtsp_get( rtsp
);
428 if( !rest
) return -1;
434 rest
= rtsp_get( rtsp
);
435 if( !rest
) return -1;
437 if( !strncasecmp( rest
, "CSeq:", 5 ) )
438 sscanf( rest
, "%*s %u", &seq
);
444 //fprintf(stderr, "warning: cseq not recognized!\n");
448 /* lets make the server happy */
449 rtsp_put( rtsp
, "RTSP/1.0 451 Parameter Not Understood" );
450 rest
= xmalloc(sizeof("Cseq: ") + 3 * sizeof(int));
451 sprintf( rest
,"CSeq: %u", seq
);
452 rtsp_put( rtsp
, rest
);
453 rtsp_put( rtsp
, "" );
455 i
= rtsp
->pf_read( rtsp
->p_userdata
, (unsigned char*)buffer
, size
);
459 i
= rtsp
->pf_read( rtsp
->p_userdata
, (unsigned char*)buffer
+ 4, size
- 4 );
463 else i
= rtsp
->pf_read( rtsp
->p_userdata
, (unsigned char*)buffer
, size
);
465 //fprintf( stderr, "<< %d of %d bytes\n", i, size );
471 * connect to a rtsp server
474 int rtsp_connect( rtsp_client_t
*rtsp
, const char *psz_mrl
,
475 const char *psz_user_agent
)
480 unsigned int hostend
, pathbegin
, i
;
482 if( !psz_mrl
) return -1;
483 s
= xmalloc( sizeof(rtsp_t
) );
486 if( !strncmp( psz_mrl
, "rtsp://", 7 ) ) psz_mrl
+= 7;
487 mrl_ptr
= strdup( psz_mrl
);
489 for( i
=0; i
<MAX_FIELDS
; i
++ )
492 s
->scheduled
[i
]=NULL
;
496 s
->port
= 554; /* rtsp standard port */
498 s
->mrl
= strdup(psz_mrl
);
507 if( psz_user_agent
) s
->user_agent
= strdup( psz_user_agent
);
508 else s
->user_agent
= strdup( "User-Agent: RealMedia Player Version "
509 "6.0.9.1235 (linux-2.0-libc6-i386-gcc2.95)" );
511 slash
= strchr( mrl_ptr
, '/' );
512 colon
= strchr( mrl_ptr
, ':' );
514 if( !slash
) slash
= mrl_ptr
+ strlen(mrl_ptr
) + 1;
515 if( !colon
) colon
= slash
;
516 if( colon
> slash
) colon
= slash
;
518 pathbegin
= slash
- mrl_ptr
;
519 hostend
= colon
- mrl_ptr
;
521 s
->host
= xmalloc(hostend
+1);
522 strncpy( s
->host
, mrl_ptr
, hostend
);
523 s
->host
[hostend
] = 0;
525 if( pathbegin
< strlen(mrl_ptr
) ) s
->path
= strdup(mrl_ptr
+pathbegin
+1);
528 char buffer
[pathbegin
-hostend
];
530 strncpy( buffer
, mrl_ptr
+hostend
+1, pathbegin
-hostend
-1 );
531 buffer
[pathbegin
-hostend
-1] = 0;
532 s
->port
= atoi(buffer
);
533 if( s
->port
< 0 || s
->port
> 65535 ) s
->port
= 554;
537 //fprintf( stderr, "got mrl: %s %i %s\n", s->host, s->port, s->path );
539 s
->s
= rtsp
->pf_connect( rtsp
->p_userdata
, s
->host
, s
->port
);
543 //fprintf(stderr, "rtsp: failed to connect to '%s'\n", s->host);
548 s
->server_state
= RTSP_CONNECTED
;
550 /* now lets send an options request. */
551 rtsp_schedule_field( rtsp
, "CSeq: 1");
552 rtsp_schedule_field( rtsp
, s
->user_agent
);
553 rtsp_schedule_field( rtsp
, "ClientChallenge: "
554 "9e26d33f2984236010ef6253fb1887f7");
555 rtsp_schedule_field( rtsp
, "PlayerStarttime: [28/03/2003:22:50:23 00:00]");
556 rtsp_schedule_field( rtsp
, "CompanyID: KnKV4M4I/B2FjJ1TToLycw==" );
557 rtsp_schedule_field( rtsp
, "GUID: 00000000-0000-0000-0000-000000000000" );
558 rtsp_schedule_field( rtsp
, "RegionData: 0" );
559 rtsp_schedule_field( rtsp
, "ClientID: "
560 "Linux_2.4_6.0.9.1235_play32_RN01_EN_586" );
561 /*rtsp_schedule_field( rtsp, "Pragma: initiate-session" );*/
562 rtsp_request_options( rtsp
, NULL
);
568 * closes an rtsp connection
571 void rtsp_close( rtsp_client_t
*rtsp
)
573 if( rtsp
->p_private
->server_state
)
575 /* TODO: send a TEAROFF */
576 rtsp
->pf_disconnect( rtsp
->p_userdata
);
579 free( rtsp
->p_private
->path
);
580 free( rtsp
->p_private
->host
);
581 free( rtsp
->p_private
->mrl
);
582 free( rtsp
->p_private
->session
);
583 free( rtsp
->p_private
->user_agent
);
584 free( rtsp
->p_private
->server
);
585 rtsp_free_answers( rtsp
);
586 rtsp_unschedule_all( rtsp
);
587 free( rtsp
->p_private
);
591 * search in answers for tags. returns a pointer to the content
592 * after the first matched tag. returns NULL if no match found.
595 char *rtsp_search_answers( rtsp_client_t
*rtsp
, const char *tag
)
601 if(rtsp
->p_private
->answers
== NULL
|| tag
== NULL
)
604 answers
= rtsp
->p_private
->answers
;
606 for (i
= 0; i
< MAX_FIELDS
; ++i
) {
607 if (answers
[i
] == NULL
)
610 if (!strncasecmp(answers
[i
], tag
, strlen(tag
))){
611 ptr
= strchr(answers
[i
], ':');
614 return answers
[i
] + strlen(answers
[i
]); /* no payload => empty string */
616 for (++ptr
; *ptr
== ' '; ++ptr
)
627 * session id management
630 void rtsp_set_session( rtsp_client_t
*rtsp
, const char *id
)
632 free( rtsp
->p_private
->session
);
633 rtsp
->p_private
->session
= strdup(id
);
636 char *rtsp_get_session( rtsp_client_t
*rtsp
)
638 return rtsp
->p_private
->session
;
641 char *rtsp_get_mrl( rtsp_client_t
*rtsp
)
643 return rtsp
->p_private
->mrl
;
647 * schedules a field for transmission
650 void rtsp_schedule_field( rtsp_client_t
*rtsp
, const char *data
)
652 access_t
* p_access
= (access_t
*)rtsp
->p_userdata
;
656 if( rtsp
->p_private
== NULL
|| data
== NULL
)
659 pptr
= rtsp
->p_private
->scheduled
;
661 for (i
= 0; i
< MAX_FIELDS
; ++i
) {
662 if (pptr
[i
] == NULL
) {
663 pptr
[i
] = strdup(data
);
668 if (i
== MAX_FIELDS
) {
669 msg_Warn (p_access
, "Unable to schedule '%s': the buffer is full!", data
);
674 * removes the first scheduled field which prefix matches string.
677 void rtsp_unschedule_field( rtsp_client_t
*rtsp
, const char *needle
)
682 if (rtsp
->p_private
== NULL
|| needle
== NULL
)
685 pptr
= rtsp
->p_private
->scheduled
;
687 for (i
= 0; i
< MAX_FIELDS
; ++i
) {
691 if (!strncmp (pptr
[i
], needle
, strlen(needle
))) {
698 for (i
++; i
< MAX_FIELDS
&& pptr
[i
]; ++i
) {
702 if (i
< MAX_FIELDS
) {
707 static void pp_free_helper_ (char **pptr
, int max_length
) {
710 for (i
= 0; i
< max_length
; ++i
) {
720 * unschedule all fields
723 void rtsp_unschedule_all( rtsp_client_t
*rtsp
)
725 if (rtsp
->p_private
== NULL
)
728 pp_free_helper_ (rtsp
->p_private
->scheduled
, MAX_FIELDS
);
734 void rtsp_free_answers( rtsp_client_t
*rtsp
)
736 if (rtsp
->p_private
== NULL
)
739 pp_free_helper_ (rtsp
->p_private
->answers
, MAX_FIELDS
);