]> git.sesse.net Git - vlc/blob - modules/access/mms/mmsh.c
* mmsh: fixed first packet handling.
[vlc] / modules / access / mms / mmsh.c
1 /*****************************************************************************
2  * mmsh.c:
3  *****************************************************************************
4  * Copyright (C) 2001, 2002 VideoLAN
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28
29 #include <vlc/vlc.h>
30 #include <vlc/input.h>
31
32 #include "vlc_playlist.h"
33
34 #include "network.h"
35 #include "asf.h"
36 #include "buffer.h"
37
38 #include "mms.h"
39 #include "mmsh.h"
40
41 /* TODO:
42  *  - http_proxy
43  *  - authentication
44  */
45
46 /*****************************************************************************
47  * Local prototypes
48  *****************************************************************************/
49 int  E_(MMSHOpen)  ( input_thread_t * );
50 void E_(MMSHClose) ( input_thread_t * );
51
52 static ssize_t  Read( input_thread_t *, byte_t *, size_t );
53 static ssize_t  ReadRedirect( input_thread_t *, byte_t *, size_t );
54 static void     Seek( input_thread_t *, off_t );
55
56 static int      Start( input_thread_t *, off_t );
57 static void     Stop( input_thread_t * );
58 static int      GetPacket( input_thread_t *, chunk_t * );
59
60 /****************************************************************************
61  * Open: connect to ftp server and ask for file
62  ****************************************************************************/
63 int  E_( MMSHOpen )  ( input_thread_t *p_input )
64 {
65     access_sys_t    *p_sys;
66
67     char            *psz;
68     char            *psz_location = NULL;
69     int             i_code;
70
71     vlc_value_t     val;
72
73     /* init p_sys */
74     p_input->p_access_data = p_sys = malloc( sizeof( access_sys_t ) );
75     p_sys->i_proto = MMS_PROTO_HTTP;
76
77     p_sys->fd       = -1;
78     p_sys->i_request_context = 1;
79     p_sys->b_broadcast = VLC_TRUE;
80     p_sys->p_packet = NULL;
81     p_sys->i_packet_sequence = 0;
82     p_sys->i_packet_used = 0;
83     p_sys->i_packet_length = 0;
84     p_sys->i_pos = 0;
85     p_sys->i_request_context = 1;
86     E_( GenerateGuid )( &p_sys->guid );
87
88     /* open a tcp connection */
89     vlc_UrlParse( &p_sys->url, p_input->psz_name, 0 );
90     if( p_sys->url.psz_host == NULL && *p_sys->url.psz_host == '\0' )
91     {
92         msg_Err( p_input, "invalid host" );
93         goto error;
94     }
95     if( p_sys->url.i_port <= 0 )
96     {
97         p_sys->url.i_port = 80;
98     }
99
100     if( ( p_sys->fd = net_OpenTCP( p_input, p_sys->url.psz_host,
101                                             p_sys->url.i_port ) ) < 0 )
102     {
103         msg_Err( p_input, "cannot connect to%s:%d", p_sys->url.psz_host, p_sys->url.i_port );
104         goto error;
105     }
106
107     /* send first request */
108     net_Printf( VLC_OBJECT(p_input), p_sys->fd,
109                 "GET %s HTTP/1.0\r\n"
110                 "Accept: */*\r\n"
111                 "User-Agent: NSPlayer/4.1.0.3856\r\n"
112                 "Host: %s:%d\r\n"
113                 "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%d,max-duration=0\r\n"
114                 "Pragma: xClientGUID={"GUID_FMT"}\r\n"
115                 "Connection: Close\r\n",
116                 ( p_sys->url.psz_path == NULL || *p_sys->url.psz_path == '\0' ) ? "/" : p_sys->url.psz_path,
117                 p_sys->url.psz_host, p_sys->url.i_port,
118                 p_sys->i_request_context++,
119                 GUID_PRINT( p_sys->guid ) );
120
121     if( net_Printf( VLC_OBJECT(p_input), p_sys->fd, "\r\n" ) < 0 )
122     {
123         msg_Err( p_input, "failed to send request" );
124         goto error;
125     }
126
127     /* Receive the http header */
128     if( ( psz = net_Gets( VLC_OBJECT(p_input), p_sys->fd ) ) == NULL )
129     {
130         msg_Err( p_input, "failed to read answer" );
131         goto error;
132     }
133     if( strncmp( psz, "HTTP/1.", 7 ) )
134     {
135         msg_Err( p_input, "invalid HTTP reply '%s'", psz );
136         free( psz );
137         goto error;
138     }
139     i_code = atoi( &psz[9] );
140     if( i_code >= 400 )
141     {
142         msg_Err( p_input, "error: %s", psz );
143         free( psz );
144         goto error;
145     }
146
147     msg_Dbg( p_input, "HTTP reply '%s'", psz );
148     free( psz );
149     for( ;; )
150     {
151         char *psz = net_Gets( p_input, p_sys->fd );
152         char *p;
153
154         if( psz == NULL )
155         {
156             msg_Err( p_input, "failed to read answer" );
157             goto error;
158         }
159
160         if( *psz == '\0' )
161         {
162             free( psz );
163             break;
164         }
165
166         if( ( p = strchr( psz, ':' ) ) == NULL )
167         {
168             msg_Err( p_input, "malformed header line: %s", psz );
169             free( psz );
170             goto error;
171         }
172         *p++ = '\0';
173         while( *p == ' ' ) p++;
174
175         /* FIXME FIXME test Content-Type to see if it's a plain stream or an
176          * asx FIXME */
177         if( !strcasecmp( psz, "Pragma" ) )
178         {
179             if( strstr( p, "features" ) )
180             {
181                 /* FIXME, it is a bit badly done here ..... */
182                 if( strstr( p, "broadcast" ) )
183                 {
184                     msg_Dbg( p_input, "stream type = broadcast" );
185                     p_sys->b_broadcast = VLC_TRUE;
186                 }
187                 else if( strstr( p, "seekable" ) )
188                 {
189                     msg_Dbg( p_input, "stream type = seekable" );
190                     p_sys->b_broadcast = VLC_FALSE;
191                 }
192                 else
193                 {
194                     msg_Warn( p_input, "unknow stream types (%s)", p );
195                     p_sys->b_broadcast = VLC_FALSE;
196                 }
197             }
198         }
199         else if( !strcasecmp( psz, "Location" ) )
200         {
201             psz_location = strdup( p );
202         }
203
204         free( psz );
205     }
206
207     /* Handle the redirection */
208     if( ( i_code == 301 || i_code == 302 ||
209           i_code == 303 || i_code == 307 ) &&
210         psz_location && *psz_location )
211     {
212         playlist_t * p_playlist;
213
214         msg_Dbg( p_input, "redirection to %s", psz_location );
215         net_Close( p_sys->fd ); p_sys->fd = -1;
216
217         p_playlist = vlc_object_find( p_input, VLC_OBJECT_PLAYLIST, FIND_PARENT );
218         if( !p_playlist )
219         {
220             msg_Err( p_input, "redirection failed: can't find playlist" );
221             goto error;
222         }
223         p_playlist->pp_items[p_playlist->i_index]->b_autodeletion = VLC_TRUE;
224         playlist_Add( p_playlist, psz_location, psz_location,
225                       PLAYLIST_INSERT | PLAYLIST_GO,
226                       p_playlist->i_index + 1 );
227         vlc_object_release( p_playlist );
228
229         p_input->pf_read = ReadRedirect;
230         p_input->pf_seek = NULL;
231         p_input->pf_set_program = input_SetProgram;
232         p_input->pf_set_area = NULL;
233
234         /* *** finished to set some variable *** */
235         vlc_mutex_lock( &p_input->stream.stream_lock );
236         p_input->stream.b_pace_control = 0;
237         p_input->stream.p_selected_area->i_size = 0;
238         p_input->stream.b_seekable = 0;
239         p_input->stream.p_selected_area->i_tell = 0;
240         p_input->stream.i_method = INPUT_METHOD_NETWORK;
241         vlc_mutex_unlock( &p_input->stream.stream_lock );
242
243         return VLC_SUCCESS;
244     }
245
246     /* Read the asf header */
247     p_sys->i_header = 0;
248     p_sys->p_header = NULL;
249     for( ;; )
250     {
251         chunk_t ck;
252         if( GetPacket( p_input, &ck ) ||
253             ck.i_type != 0x4824 )
254         {
255             break;
256         }
257
258         if( ck.i_data > 0 )
259         {
260             p_sys->i_header += ck.i_data;
261             p_sys->p_header = realloc( p_sys->p_header, p_sys->i_header );
262             memcpy( &p_sys->p_header[p_sys->i_header - ck.i_data],
263                     ck.p_data, ck.i_data );
264         }
265     }
266     msg_Dbg( p_input, "complete header size=%d", p_sys->i_header );
267     if( p_sys->i_header <= 0 )
268     {
269         msg_Err( p_input, "header size == 0" );
270         goto error;
271     }
272     /* close this connection */
273     net_Close( p_sys->fd ); p_sys->fd = -1;
274
275     /* *** parse header and get stream and their id *** */
276     /* get all streams properties,
277      *
278      * TODO : stream bitrates properties(optional)
279      *        and bitrate mutual exclusion(optional) */
280     E_( asf_HeaderParse )( &p_sys->asfh,
281                            p_sys->p_header, p_sys->i_header );
282     msg_Dbg( p_input, "packet count=%lld packet size=%d",
283              p_sys->asfh.i_data_packets_count,
284              p_sys->asfh.i_min_data_packet_size );
285
286     E_( asf_StreamSelect)( &p_sys->asfh,
287                            config_GetInt( p_input, "mms-maxbitrate" ),
288                            config_GetInt( p_input, "mms-all" ),
289                            config_GetInt( p_input, "audio" ),
290                            config_GetInt( p_input, "video" ) );
291
292     if( Start( p_input, 0 ) )
293     {
294         msg_Err( p_input, "cannot start stream" );
295         goto error;
296     }
297
298     /* *** set exported functions *** */
299     p_input->pf_read = Read;
300     p_input->pf_seek = Seek;
301     p_input->pf_set_program = input_SetProgram;
302     p_input->pf_set_area = NULL;
303
304     p_input->p_private = NULL;
305     p_input->i_mtu = 3 * p_sys->asfh.i_min_data_packet_size;
306
307     /* *** finished to set some variable *** */
308     vlc_mutex_lock( &p_input->stream.stream_lock );
309     p_input->stream.b_pace_control = 0;
310     if( p_sys->b_broadcast )
311     {
312         p_input->stream.p_selected_area->i_size = 0;
313         p_input->stream.b_seekable = 0;
314     }
315     else
316     {
317         p_input->stream.p_selected_area->i_size = p_sys->asfh.i_file_size;
318         p_input->stream.b_seekable = 1;
319     }
320     p_input->stream.p_selected_area->i_tell = 0;
321     p_input->stream.i_method = INPUT_METHOD_NETWORK;
322     vlc_mutex_unlock( &p_input->stream.stream_lock );
323
324     /* Update default_pts to a suitable value for mms access */
325     var_Get( p_input, "mms-caching", &val );
326     p_input->i_pts_delay = val.i_int * 1000;
327
328     return VLC_SUCCESS;
329
330 error:
331     vlc_UrlClean( &p_sys->url );
332     if( p_sys->fd > 0 )
333     {
334         net_Close( p_sys->fd  );
335     }
336     free( p_sys );
337     return VLC_EGENERIC;
338 }
339
340 /*****************************************************************************
341  * Close: free unused data structures
342  *****************************************************************************/
343 void E_( MMSHClose )( input_thread_t *p_input )
344 {
345     access_sys_t    *p_sys   = p_input->p_access_data;
346
347     msg_Dbg( p_input, "stopping stream" );
348
349     Stop( p_input );
350
351     free( p_sys );
352 }
353
354 /*****************************************************************************
355  * Seek: try to go at the right place
356  *****************************************************************************/
357 static void Seek( input_thread_t * p_input, off_t i_pos )
358 {
359     access_sys_t *p_sys = p_input->p_access_data;
360     chunk_t      ck;
361     off_t        i_offset;
362     off_t        i_packet;
363
364     i_packet = ( i_pos - p_sys->i_header ) / p_sys->asfh.i_min_data_packet_size;
365     i_offset = ( i_pos - p_sys->i_header ) % p_sys->asfh.i_min_data_packet_size;
366
367     msg_Dbg( p_input, "seeking to "I64Fd, i_pos );
368
369     vlc_mutex_lock( &p_input->stream.stream_lock );
370
371     Stop( p_input );
372     Start( p_input, i_packet * p_sys->asfh.i_min_data_packet_size );
373
374     for( ;; )
375     {
376         if( GetPacket( p_input, &ck ) )
377         {
378             break;
379         }
380
381         /* skip headers */
382         if( ck.i_type != 0x4824 )
383         {
384             break;
385         }
386         msg_Warn( p_input, "skipping header" );
387     }
388
389     p_sys->i_pos = i_pos;
390     p_sys->i_packet_used += i_offset;
391
392     p_input->stream.p_selected_area->i_tell = i_pos;
393     vlc_mutex_unlock( &p_input->stream.stream_lock );
394 }
395
396 /*****************************************************************************
397  * Read:
398  *****************************************************************************/
399 static ssize_t ReadRedirect( input_thread_t *p_input, byte_t *p, size_t i )
400 {
401     return 0;
402 }
403
404 /*****************************************************************************
405  * Read:
406  *****************************************************************************/
407 static ssize_t Read        ( input_thread_t * p_input, byte_t * p_buffer,
408                              size_t i_len )
409 {
410     access_sys_t *p_sys = p_input->p_access_data;
411     size_t       i_copy;
412     size_t       i_data = 0;
413
414     while( i_data < i_len )
415     {
416         if( p_sys->i_pos < p_sys->i_header )
417         {
418             i_copy = __MIN( p_sys->i_header - p_sys->i_pos, i_len - i_data );
419             memcpy( &p_buffer[i_data], &p_sys->p_header[p_sys->i_pos], i_copy );
420
421             i_data += i_copy;
422             p_sys->i_pos += i_copy;
423         }
424         else if( p_sys->i_packet_used < p_sys->i_packet_length )
425         {
426             i_copy = __MIN( p_sys->i_packet_length - p_sys->i_packet_used,
427                             i_len - i_data );
428
429             memcpy( &p_buffer[i_data],
430                     &p_sys->p_packet[p_sys->i_packet_used],
431                     i_copy );
432
433             i_data += i_copy;
434             p_sys->i_packet_used += i_copy;
435             p_sys->i_pos += i_copy;
436         }
437         else if( p_sys->i_packet_length > 0 &&
438                  (int)p_sys->i_packet_used < p_sys->asfh.i_min_data_packet_size )
439         {
440             i_copy = __MIN( p_sys->asfh.i_min_data_packet_size - p_sys->i_packet_used,
441                             i_len - i_data );
442
443             memset( &p_buffer[i_data], 0, i_copy );
444
445             i_data += i_copy;
446             p_sys->i_packet_used += i_copy;
447             p_sys->i_pos += i_copy;
448         }
449         else
450         {
451             chunk_t ck;
452             if( GetPacket( p_input, &ck ) )
453             {
454                 return -1;
455             }
456             if( ck.i_type == 0x4824 )
457             {
458                 p_sys->i_packet_used = 0;
459                 p_sys->i_packet_length = 0;
460             }
461         }
462     }
463
464     return( i_data );
465 }
466
467 /*****************************************************************************
468  *
469  *****************************************************************************/
470 static int Start( input_thread_t *p_input, off_t i_pos )
471 {
472     access_sys_t *p_sys = p_input->p_access_data;
473     int  i_streams = 0;
474     int  i;
475     char *psz;
476
477     msg_Dbg( p_input, "starting stream" );
478
479     if( ( p_sys->fd = net_OpenTCP( p_input, p_sys->url.psz_host,
480                                             p_sys->url.i_port ) ) < 0 )
481     {
482         /* should not occur */
483         msg_Err( p_input, "cannot connect to the server" );
484         return VLC_EGENERIC;
485     }
486
487     for( i = 1; i < 128; i++ )
488     {
489         if( p_sys->asfh.stream[i].i_selected )
490         {
491             i_streams++;
492         }
493     }
494
495     if( i_streams <= 0 )
496     {
497         msg_Err( p_input, "no stream selected" );
498         return VLC_EGENERIC;
499     }
500     net_Printf( VLC_OBJECT(p_input), p_sys->fd,
501                 "GET %s HTTP/1.0\r\n"
502                 "Accept: */*\r\n"
503                 "User-Agent: NSPlayer/4.1.0.3856\r\n"
504                 "Host: %s:%d\r\n",
505                 ( p_sys->url.psz_path == NULL || *p_sys->url.psz_path == '\0' ) ? "/" : p_sys->url.psz_path,
506                 p_sys->url.psz_host, p_sys->url.i_port );
507     if( p_sys->b_broadcast )
508     {
509         net_Printf( VLC_OBJECT(p_input), p_sys->fd,
510                     "Pragma: no-cache,rate=1.000000,request-context=%d\r\n",
511                     p_sys->i_request_context++ );
512     }
513     else
514     {
515         net_Printf( VLC_OBJECT(p_input), p_sys->fd,
516                     "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=%u:%u,request-context=%d,max-duration=0\r\n",
517                     (uint32_t)((i_pos >> 32)&0xffffffff),
518                     (uint32_t)(i_pos&0xffffffff),
519                     p_sys->i_request_context++ );
520     }
521     net_Printf( VLC_OBJECT(p_input), p_sys->fd,
522                 "Pragma: xPlayStrm=1\r\n"
523                 "Pragma: xClientGUID={"GUID_FMT"}\r\n"
524                 "Pragma: stream-switch-count=%d\r\n"
525                 "Pragma: stream-switch-entry=",
526                 GUID_PRINT( p_sys->guid ),
527                 i_streams);
528
529     for( i = 1; i < 128; i++ )
530     {
531         if( p_sys->asfh.stream[i].i_cat != ASF_STREAM_UNKNOWN )
532         {
533             int i_select = 2;
534             if( p_sys->asfh.stream[i].i_selected )
535             {
536                 i_select = 0;
537             }
538
539             net_Printf( VLC_OBJECT(p_input), p_sys->fd,
540                         "ffff:%d:%d ", i, i_select );
541         }
542     }
543     net_Printf( VLC_OBJECT(p_input), p_sys->fd, "\r\n" );
544     net_Printf( VLC_OBJECT(p_input), p_sys->fd, "Connection: Close\r\n" );
545
546     if( net_Printf( VLC_OBJECT(p_input), p_sys->fd, "\r\n" ) < 0 )
547     {
548         msg_Err( p_input, "failed to send request" );
549         return VLC_EGENERIC;
550     }
551
552     if( ( psz = net_Gets( VLC_OBJECT(p_input), p_sys->fd ) ) == NULL )
553     {
554         msg_Err( p_input, "cannot read data" );
555         return VLC_EGENERIC;
556     }
557     if( atoi( &psz[9] ) >= 400 )
558     {
559         msg_Err( p_input, "error: %s", psz );
560         free( psz );
561         return VLC_EGENERIC;
562     }
563     msg_Dbg( p_input, "HTTP reply '%s'", psz );
564     free( psz );
565
566     /* FIXME check HTTP code */
567     for( ;; )
568     {
569         char *psz = net_Gets( p_input, p_sys->fd );
570         if( psz == NULL )
571         {
572             msg_Err( p_input, "cannot read data" );
573             return VLC_EGENERIC;
574         }
575         if( *psz == '\0' )
576         {
577             free( psz );
578             break;
579         }
580         msg_Dbg( p_input, "%s", psz );
581         free( psz );
582     }
583
584     p_sys->i_packet_used   = 0;
585     p_sys->i_packet_length = 0;
586
587     return VLC_SUCCESS;
588 }
589
590 /*****************************************************************************
591  *
592  *****************************************************************************/
593 static void Stop( input_thread_t *p_input )
594 {
595     access_sys_t *p_sys = p_input->p_access_data;
596
597     msg_Dbg( p_input, "closing stream" );
598     net_Close( p_sys->fd ); p_sys->fd = -1;
599 }
600
601 /*****************************************************************************
602  *
603  *****************************************************************************/
604 static int GetPacket( input_thread_t * p_input, chunk_t *p_ck )
605 {
606     access_sys_t *p_sys = p_input->p_access_data;
607
608     /* Read the chunk header */
609     if( net_Read( p_input, p_sys->fd, p_sys->buffer, 12, VLC_TRUE ) < 12 )
610     {
611         /* msg_Err( p_input, "cannot read data" ); */
612         return VLC_EGENERIC;
613     }
614
615     p_ck->i_type      = GetWLE( p_sys->buffer);
616     p_ck->i_size      = GetWLE( p_sys->buffer + 2);
617     p_ck->i_sequence  = GetDWLE( p_sys->buffer + 4);
618     p_ck->i_unknown   = GetWLE( p_sys->buffer + 8);
619     p_ck->i_size2     = GetWLE( p_sys->buffer + 10);
620     p_ck->p_data      = p_sys->buffer + 12;
621     p_ck->i_data      = p_ck->i_size2 - 8;
622
623     if( p_ck->i_type == 0x4524 )   // Transfer complete
624     {
625         msg_Warn( p_input, "EOF" );
626         return VLC_EGENERIC;
627     }
628     else if( p_ck->i_type != 0x4824 && p_ck->i_type != 0x4424 )
629     {
630         msg_Err( p_input, "invalid chunk FATAL" );
631         return VLC_EGENERIC;
632     }
633
634     if( net_Read( p_input, p_sys->fd, &p_sys->buffer[12], p_ck->i_data, VLC_TRUE ) < p_ck->i_data )
635     {
636         msg_Err( p_input, "cannot read data" );
637         return VLC_EGENERIC;
638     }
639
640     if( p_sys->i_packet_sequence != 0 &&
641         p_ck->i_sequence != p_sys->i_packet_sequence )
642     {
643         msg_Warn( p_input, "packet lost ?" );
644     }
645
646     p_sys->i_packet_sequence = p_ck->i_sequence + 1;
647     p_sys->i_packet_used   = 0;
648     p_sys->i_packet_length = p_ck->i_data;
649     p_sys->p_packet        = p_ck->p_data;
650
651     return VLC_SUCCESS;
652 }