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