]> git.sesse.net Git - vlc/blob - modules/access/rtsp/rtsp.c
a9133be62fcc684dff1190afc2e437824ba031ab
[vlc] / modules / access / rtsp / rtsp.c
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
7  * $Id$
8  *
9  * Authors: Gildas Bazin <gbazin@videolan.org>
10  *          Adapted from xine which itself adapted it from joschkas real tools.
11  *
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.
16  *
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.
21  *
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  *****************************************************************************/
26
27 #include <vlc/vlc.h>
28
29 #include "rtsp.h"
30
31 #define BUF_SIZE 4096
32 #define HEADER_SIZE 1024
33 #define MAX_FIELDS 256
34
35 struct rtsp_s {
36
37   int           s;
38
39   char         *host;
40   int           port;
41   char         *path;
42   char         *mrl;
43   char         *user_agent;
44
45   char         *server;
46   unsigned int  server_state;
47   uint32_t      server_caps;
48
49   unsigned int  cseq;
50   char         *session;
51
52   char        *answers[MAX_FIELDS];   /* data of last message */
53   char        *scheduled[MAX_FIELDS]; /* will be sent with next message */
54 };
55
56 /*
57  * constants
58  */
59
60 const char rtsp_protocol_version[]="RTSP/1.0";
61
62 /* server states */
63 #define RTSP_CONNECTED 1
64 #define RTSP_INIT      2
65 #define RTSP_READY     4
66 #define RTSP_PLAYING   8
67 #define RTSP_RECORDING 16
68
69 /* server capabilities */
70 #define RTSP_OPTIONS       0x001
71 #define RTSP_DESCRIBE      0x002
72 #define RTSP_ANNOUNCE      0x004
73 #define RTSP_SETUP         0x008
74 #define RTSP_GET_PARAMETER 0x010
75 #define RTSP_SET_PARAMETER 0x020
76 #define RTSP_TEARDOWN      0x040
77 #define RTSP_PLAY          0x080
78 #define RTSP_RECORD        0x100
79
80 /*
81  * rtsp_get gets a line from stream
82  * and returns a null terminated string (must be freed).
83  */
84  
85 static char *rtsp_get( rtsp_client_t *rtsp )
86 {
87   char *psz_buffer = malloc( BUF_SIZE );
88   char *psz_string = NULL;
89
90   if( rtsp->pf_read_line( rtsp->p_userdata, (uint8_t*)psz_buffer, (unsigned int)BUF_SIZE ) >= 0 )
91   {
92     //printf( "<< '%s'\n", psz_buffer );
93       psz_string = strdup( psz_buffer );
94   }
95
96   free( psz_buffer );
97   return psz_string;
98 }
99
100
101 /*
102  * rtsp_put puts a line on stream
103  */
104
105 static int rtsp_put( rtsp_client_t *rtsp, const char *psz_string )
106 {
107     unsigned int i_buffer = strlen( psz_string );
108     char *psz_buffer = malloc( i_buffer + 3 );
109     int i_ret;
110
111     strcpy( psz_buffer, psz_string );
112     psz_buffer[i_buffer] = '\r'; psz_buffer[i_buffer+1] = '\n';
113     psz_buffer[i_buffer+2] = 0;
114
115     i_ret = rtsp->pf_write( rtsp->p_userdata, (uint8_t*)psz_buffer, i_buffer + 2 );
116
117     free( psz_buffer );
118     return i_ret;
119 }
120
121 /*
122  * extract server status code
123  */
124
125 static int rtsp_get_status_code( rtsp_client_t *rtsp, const char *psz_string )
126 {
127     char psz_buffer[4];
128     int i_code = 0;
129
130     if( !strncmp( psz_string, "RTSP/1.0", sizeof("RTSP/1.0") - 1 ) )
131     {
132         memcpy( psz_buffer, psz_string + sizeof("RTSP/1.0"), 3 );
133         psz_buffer[3] = 0;
134         i_code = atoi( psz_buffer );
135     }
136     else if( !strncmp( psz_string, "SET_PARAMETER", 8 ) )
137     {
138         return RTSP_STATUS_SET_PARAMETER;
139     }
140
141     if( i_code != 200 )
142     {
143         //fprintf( stderr, "librtsp: server responds: '%s'\n", psz_string );
144     }
145
146     return i_code;
147 }
148
149 /*
150  * send a request
151  */
152
153 static int rtsp_send_request( rtsp_client_t *rtsp, const char *psz_type,
154                               const char *psz_what )
155 {
156     char **ppsz_payload = rtsp->p_private->scheduled;
157     char *psz_buffer;
158     int i_ret;
159
160     psz_buffer = malloc( strlen(psz_type) + strlen(psz_what) +
161                          sizeof("RTSP/1.0") + 2 );
162
163     sprintf( psz_buffer, "%s %s %s", psz_type, psz_what, "RTSP/1.0" );
164     i_ret = rtsp_put( rtsp, psz_buffer );
165     free( psz_buffer );
166
167     if( ppsz_payload )
168         while( *ppsz_payload )
169         {
170             rtsp_put( rtsp, *ppsz_payload );
171             ppsz_payload++;
172         }
173     rtsp_put( rtsp, "" );
174     rtsp_unschedule_all( rtsp );
175
176     return i_ret;
177 }
178
179 /*
180  * schedule standard fields
181  */
182
183 static void rtsp_schedule_standard( rtsp_client_t *rtsp )
184 {
185     char tmp[17];
186
187     sprintf( tmp, "Cseq: %u", rtsp->p_private->cseq);
188     rtsp_schedule_field( rtsp, tmp );
189
190     if( rtsp->p_private->session )
191     {
192         char *buf;
193         buf = malloc( strlen(rtsp->p_private->session) + 15 );
194         sprintf( buf, "Session: %s", rtsp->p_private->session );
195         rtsp_schedule_field( rtsp, buf );
196         free( buf );
197     }
198 }
199
200 /*
201  * get the answers, if server responses with something != 200, return NULL
202  */
203
204 static int rtsp_get_answers( rtsp_client_t *rtsp )
205 {
206     char *answer = NULL;
207     unsigned int answer_seq;
208     char **answer_ptr = rtsp->p_private->answers;
209     int code;
210     int ans_count = 0;
211
212     answer = rtsp_get( rtsp );
213     if( !answer ) return 0;
214     code = rtsp_get_status_code( rtsp, answer );
215     free( answer );
216
217     rtsp_free_answers( rtsp );
218
219     do { /* while we get answer lines */
220
221       answer = rtsp_get( rtsp );
222       if( !answer ) return 0;
223
224       if( !strncasecmp( answer, "Cseq:", 5 ) )
225       {
226           sscanf( answer, "%*s %u", &answer_seq );
227           if( rtsp->p_private->cseq != answer_seq )
228           {
229             //fprintf( stderr, "warning: Cseq mismatch. got %u, assumed %u",
230             //       answer_seq, rtsp->p_private->cseq );
231
232               rtsp->p_private->cseq = answer_seq;
233           }
234       }
235       if( !strncasecmp( answer, "Server:", 7 ) )
236       {
237           char *buf = malloc( strlen(answer) );
238           sscanf( answer, "%*s %s", buf );
239           if( rtsp->p_private->server ) free( rtsp->p_private->server );
240           rtsp->p_private->server = buf;
241       }
242       if( !strncasecmp( answer, "Session:", 8 ) )
243       {
244           char *buf = malloc( strlen(answer) );
245           sscanf( answer, "%*s %s", buf );
246           if( rtsp->p_private->session )
247           {
248               if( strcmp( buf, rtsp->p_private->session ) )
249               {
250                   //fprintf( stderr, 
251                   //         "rtsp: warning: setting NEW session: %s\n", buf );
252                   free( rtsp->p_private->session );
253                   rtsp->p_private->session = strdup( buf );
254               }
255           }
256           else
257           {
258               //fprintf( stderr, "setting session id to: %s\n", buf );
259               rtsp->p_private->session = strdup( buf );
260           }
261           free( buf );
262       }
263
264       *answer_ptr = answer;
265       answer_ptr++;
266     } while( (strlen(answer) != 0) && (++ans_count < MAX_FIELDS) );
267
268     rtsp->p_private->cseq++;
269
270     *answer_ptr = NULL;
271     rtsp_schedule_standard( rtsp );
272
273     return code;
274 }
275
276 /*
277  * send an ok message
278  */
279
280 int rtsp_send_ok( rtsp_client_t *rtsp )
281 {
282     char cseq[16];
283
284     rtsp_put( rtsp, "RTSP/1.0 200 OK" );
285     sprintf( cseq, "CSeq: %u", rtsp->p_private->cseq );
286     rtsp_put( rtsp, cseq );
287     rtsp_put( rtsp, "" );
288     return 0;
289 }
290
291 /*
292  * implementation of must-have rtsp requests; functions return
293  * server status code.
294  */
295
296 int rtsp_request_options( rtsp_client_t *rtsp, const char *what )
297 {
298     char *buf;
299
300     if( what ) buf = strdup(what);
301     else
302     {
303         buf = malloc( strlen(rtsp->p_private->host) + 16 );
304         sprintf( buf, "rtsp://%s:%i", rtsp->p_private->host,
305                  rtsp->p_private->port );
306     }
307     rtsp_send_request( rtsp, "OPTIONS", buf );
308     free( buf );
309
310     return rtsp_get_answers( rtsp );
311 }
312
313 int rtsp_request_describe( rtsp_client_t *rtsp, const char *what )
314 {
315     char *buf;
316
317     if( what )
318     {
319         buf = strdup(what);
320     }
321     else
322     {
323         buf = malloc( strlen(rtsp->p_private->host) +
324                       strlen(rtsp->p_private->path) + 16 );
325         sprintf( buf, "rtsp://%s:%i/%s", rtsp->p_private->host,
326                  rtsp->p_private->port, rtsp->p_private->path );
327     }
328     rtsp_send_request( rtsp, "DESCRIBE", buf );
329     free( buf );
330
331     return rtsp_get_answers( rtsp );
332 }
333
334 int rtsp_request_setup( rtsp_client_t *rtsp, const char *what )
335 {
336     rtsp_send_request( rtsp, "SETUP", what );
337     return rtsp_get_answers( rtsp );
338 }
339
340 int rtsp_request_setparameter( rtsp_client_t *rtsp, const char *what )
341 {
342     char *buf;
343
344     if( what )
345     {
346         buf = strdup(what);
347     }
348     else
349     {
350         buf = malloc( strlen(rtsp->p_private->host) +
351                       strlen(rtsp->p_private->path) + 16 );
352         sprintf( buf, "rtsp://%s:%i/%s", rtsp->p_private->host,
353                  rtsp->p_private->port, rtsp->p_private->path );
354     }
355
356     rtsp_send_request( rtsp, "SET_PARAMETER", buf );
357     free( buf );
358
359     return rtsp_get_answers( rtsp );
360 }
361
362 int rtsp_request_play( rtsp_client_t *rtsp, const char *what )
363 {
364     char *buf;
365
366     if( what )
367     {
368         buf = strdup( what );
369     }
370     else
371     {
372         buf = malloc( strlen(rtsp->p_private->host) +
373                       strlen(rtsp->p_private->path) + 16 );
374         sprintf( buf, "rtsp://%s:%i/%s", rtsp->p_private->host,
375                  rtsp->p_private->port, rtsp->p_private->path );
376     }
377
378     rtsp_send_request( rtsp, "PLAY", buf );
379     free( buf );
380
381     return rtsp_get_answers( rtsp );
382 }
383
384 int rtsp_request_tearoff( rtsp_client_t *rtsp, const char *what )
385 {
386     rtsp_send_request( rtsp, "TEAROFF", what );
387     return rtsp_get_answers( rtsp );
388 }
389
390 /*
391  * read opaque data from stream
392  */
393
394 int rtsp_read_data( rtsp_client_t *rtsp, uint8_t *buffer, unsigned int size )
395 {
396     int i, seq;
397
398     if( size >= 4 )
399     {
400         i = rtsp->pf_read( rtsp->p_userdata, (uint8_t*)buffer, (unsigned int) 4 );
401         if( i < 4 ) return i;
402
403         if( buffer[0]=='S' && buffer[1]=='E' && buffer[2]=='T' &&
404             buffer[3]=='_' )
405         {
406             char *rest = rtsp_get( rtsp );
407             if( !rest ) return -1;
408
409             seq = -1;
410             do
411             {
412                 free( rest );
413                 rest = rtsp_get( rtsp );
414                 if( !rest ) return -1;
415
416                 if( !strncasecmp( rest, "Cseq:", 5 ) )
417                     sscanf( rest, "%*s %u", &seq );
418             } while( *rest );
419             free( rest );
420
421             if( seq < 0 )
422             {
423                 //fprintf(stderr, "warning: cseq not recognized!\n");
424                 seq = 1;
425             }
426
427             /* lets make the server happy */
428             rtsp_put( rtsp, "RTSP/1.0 451 Parameter Not Understood" );
429             rest = malloc(17);
430             sprintf( rest,"CSeq: %u", seq );
431             rtsp_put( rtsp, rest );
432             rtsp_put( rtsp, "" );
433             free( rest );
434             i = rtsp->pf_read( rtsp->p_userdata, (unsigned char*)buffer, size );
435         }
436         else
437         {
438             i = rtsp->pf_read( rtsp->p_userdata, (unsigned char*)buffer + 4, size - 4 );
439             i += 4;
440         }
441     }
442     else i = rtsp->pf_read( rtsp->p_userdata, (unsigned char*)buffer, size );
443
444     //fprintf( stderr, "<< %d of %d bytes\n", i, size );
445
446     return i;
447 }
448
449 /*
450  * connect to a rtsp server
451  */
452
453 int rtsp_connect( rtsp_client_t *rtsp, const char *psz_mrl,
454                   const char *psz_user_agent )
455 {
456     rtsp_t *s;
457     char *mrl_ptr;
458     char *slash, *colon;
459     unsigned int hostend, pathbegin, i;
460
461     if( !psz_mrl ) return -1;
462     s = malloc( sizeof(rtsp_t) );
463     rtsp->p_private = s;
464
465     if( !strncmp( psz_mrl, "rtsp://", 7 ) ) psz_mrl += 7;
466     mrl_ptr = strdup( psz_mrl );
467
468     for( i=0; i<MAX_FIELDS; i++ )
469     {
470         s->answers[i]=NULL;
471         s->scheduled[i]=NULL;
472     }
473
474     s->host = NULL;
475     s->port = 554; /* rtsp standard port */
476     s->path = NULL;
477     s->mrl  = strdup(psz_mrl);
478
479     s->server = NULL;
480     s->server_state = 0;
481     s->server_caps = 0;
482
483     s->cseq = 0;
484     s->session = NULL;
485
486     if( psz_user_agent ) s->user_agent = strdup( psz_user_agent );
487     else s->user_agent = strdup( "User-Agent: RealMedia Player Version "
488                                  "6.0.9.1235 (linux-2.0-libc6-i386-gcc2.95)" );
489
490     slash = strchr( mrl_ptr, '/' );
491     colon = strchr( mrl_ptr, ':' );
492
493     if( !slash ) slash = mrl_ptr + strlen(mrl_ptr) + 1;
494     if( !colon ) colon = slash; 
495     if( colon > slash ) colon = slash;
496
497     pathbegin = slash - mrl_ptr;
498     hostend = colon - mrl_ptr;
499
500     s->host = malloc(hostend+1);
501     strncpy( s->host, mrl_ptr, hostend );
502     s->host[hostend] = 0;
503
504     if( pathbegin < strlen(mrl_ptr) ) s->path = strdup(mrl_ptr+pathbegin+1);
505     if( colon != slash )
506     {
507         char buffer[pathbegin-hostend];
508
509         strncpy( buffer, mrl_ptr+hostend+1, pathbegin-hostend-1 );
510         buffer[pathbegin-hostend-1] = 0;
511         s->port = atoi(buffer);
512         if( s->port < 0 || s->port > 65535 ) s->port = 554;
513     }
514
515     free( mrl_ptr );
516     //fprintf( stderr, "got mrl: %s %i %s\n", s->host, s->port, s->path );
517
518     s->s = rtsp->pf_connect( rtsp->p_userdata, s->host, s->port );
519
520     if( s->s < 0 )
521     {
522         //fprintf(stderr, "rtsp: failed to connect to '%s'\n", s->host);
523         rtsp_close( rtsp );
524         return -1;
525     }
526
527     s->server_state = RTSP_CONNECTED;
528
529     /* now lets send an options request. */
530     rtsp_schedule_field( rtsp, "CSeq: 1");
531     rtsp_schedule_field( rtsp, s->user_agent);
532     rtsp_schedule_field( rtsp, "ClientChallenge: "
533                                "9e26d33f2984236010ef6253fb1887f7");
534     rtsp_schedule_field( rtsp, "PlayerStarttime: [28/03/2003:22:50:23 00:00]");
535     rtsp_schedule_field( rtsp, "CompanyID: KnKV4M4I/B2FjJ1TToLycw==" );
536     rtsp_schedule_field( rtsp, "GUID: 00000000-0000-0000-0000-000000000000" );
537     rtsp_schedule_field( rtsp, "RegionData: 0" );
538     rtsp_schedule_field( rtsp, "ClientID: "
539                                "Linux_2.4_6.0.9.1235_play32_RN01_EN_586" );
540     /*rtsp_schedule_field( rtsp, "Pragma: initiate-session" );*/
541     rtsp_request_options( rtsp, NULL );
542
543     return 0;
544 }
545
546 /*
547  * closes an rtsp connection 
548  */
549
550 void rtsp_close( rtsp_client_t *rtsp )
551 {
552     if( rtsp->p_private->server_state )
553     {
554         /* TODO: send a TEAROFF */
555         rtsp->pf_disconnect( rtsp->p_userdata );
556     }
557
558     if( rtsp->p_private->path ) free( rtsp->p_private->path );
559     if( rtsp->p_private->host ) free( rtsp->p_private->host );
560     if( rtsp->p_private->mrl ) free( rtsp->p_private->mrl );
561     if( rtsp->p_private->session ) free( rtsp->p_private->session );
562     if( rtsp->p_private->user_agent ) free( rtsp->p_private->user_agent );
563     if( rtsp->p_private->server ) free( rtsp->p_private->server );
564     rtsp_free_answers( rtsp );
565     rtsp_unschedule_all( rtsp );
566     free( rtsp->p_private );
567 }
568
569 /*
570  * search in answers for tags. returns a pointer to the content
571  * after the first matched tag. returns NULL if no match found.
572  */
573
574 char *rtsp_search_answers( rtsp_client_t *rtsp, const char *tag )
575 {
576     char **answer;
577     char *ptr;
578
579     if( !rtsp->p_private->answers ) return NULL;
580     answer = rtsp->p_private->answers;
581
582     while(*answer)
583     {
584         if( !strncasecmp( *answer, tag, strlen(tag) ) )
585         {
586             ptr = strchr(*answer, ':');
587             ptr++;
588             while( *ptr == ' ' ) ptr++;
589             return ptr;
590         }
591         answer++;
592     }
593
594     return NULL;
595 }
596
597 /*
598  * session id management
599  */
600
601 void rtsp_set_session( rtsp_client_t *rtsp, const char *id )
602 {
603     if( rtsp->p_private->session ) free( rtsp->p_private->session );
604     rtsp->p_private->session = strdup(id);
605 }
606
607 char *rtsp_get_session( rtsp_client_t *rtsp )
608 {
609     return rtsp->p_private->session;
610 }
611
612 char *rtsp_get_mrl( rtsp_client_t *rtsp )
613 {
614     return rtsp->p_private->mrl;
615 }
616
617 /*
618  * schedules a field for transmission
619  */
620
621 void rtsp_schedule_field( rtsp_client_t *rtsp, const char *string )
622 {
623     int i = 0;
624
625     if( !string ) return;
626
627     while( rtsp->p_private->scheduled[i] ) i++;
628
629     rtsp->p_private->scheduled[i] = strdup(string);
630 }
631
632 /*
633  * removes the first scheduled field which prefix matches string. 
634  */
635
636 void rtsp_unschedule_field( rtsp_client_t *rtsp, const char *string )
637 {
638     char **ptr = rtsp->p_private->scheduled;
639
640     if( !string ) return;
641
642     while( *ptr )
643     {
644       if( !strncmp(*ptr, string, strlen(string)) ) break;
645     }
646     if( *ptr ) free( *ptr );
647     ptr++;
648     do
649     {
650         *(ptr-1) = *ptr;
651     } while( *ptr );
652 }
653
654 /*
655  * unschedule all fields
656  */
657
658 void rtsp_unschedule_all( rtsp_client_t *rtsp )
659 {
660     char **ptr;
661
662     if( !rtsp->p_private->scheduled ) return;
663     ptr = rtsp->p_private->scheduled;
664
665     while( *ptr )
666     {
667         free( *ptr );
668         *ptr = NULL;
669         ptr++;
670     }
671 }
672 /*
673  * free answers
674  */
675
676 void rtsp_free_answers( rtsp_client_t *rtsp )
677 {
678     char **answer;
679
680     if( !rtsp->p_private->answers ) return;
681     answer = rtsp->p_private->answers;
682
683     while( *answer )
684     {
685         free( *answer );
686         *answer = NULL;
687         answer++;
688     }
689 }