Commit e0c8d4ac authored by Laurent Aimar's avatar Laurent Aimar

* mms/* : begining of a m$-http-for-asf-streaming support.

 Proxy aren't yet supported. Use mmsh to force it, else mms will try it
at the end (after mmst and mmsu). Some http url are in fact mmsh but
not yet auto-detected.
 *Please report* not working url and any regression for mmst and mmsu.
parent 3b768aef
SOURCES_access_mms = \ SOURCES_access_mms = \
modules/access/mms/mms.c \ modules/access/mms/mms.c \
modules/access/mms/mms.h \ modules/access/mms/mms.h \
modules/access/mms/mmsh.c \
modules/access/mms/mmsh.h \
modules/access/mms/mmstu.c \
modules/access/mms/mmstu.h \
modules/access/mms/buffer.c \ modules/access/mms/buffer.c \
modules/access/mms/buffer.h \ modules/access/mms/buffer.h \
modules/access/mms/asf.h \ modules/access/mms/asf.h \
modules/access/mms/asf.c \
$(NULL) $(NULL)
/*****************************************************************************
* asf.c: MMS access plug-in
*****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN
* $Id: asf.c,v 1.1 2003/04/20 19:29:43 fenrir Exp $
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include <stdlib.h>
#include <vlc/vlc.h>
#include "asf.h"
#include "buffer.h"
static int CmpGuid( const guid_t *p_guid1, const guid_t *p_guid2 )
{
return( ( p_guid1->v1 == p_guid2->v1 &&
p_guid1->v2 == p_guid2->v2 &&
p_guid1->v3 == p_guid2->v3 &&
p_guid1->v4[0] == p_guid2->v4[0] &&
p_guid1->v4[1] == p_guid2->v4[1] &&
p_guid1->v4[2] == p_guid2->v4[2] &&
p_guid1->v4[3] == p_guid2->v4[3] &&
p_guid1->v4[4] == p_guid2->v4[4] &&
p_guid1->v4[5] == p_guid2->v4[5] &&
p_guid1->v4[6] == p_guid2->v4[6] &&
p_guid1->v4[7] == p_guid2->v4[7] ) ? 1 : 0 );
}
void E_( GenerateGuid )( guid_t *p_guid )
{
int i;
srand( mdate() & 0xffffffff );
/* FIXME should be generated using random data */
p_guid->v1 = 0xbabac001;
p_guid->v2 = ( (uint64_t)rand() << 16 ) / RAND_MAX;
p_guid->v3 = ( (uint64_t)rand() << 16 ) / RAND_MAX;
for( i = 0; i < 8; i++ )
{
p_guid->v4[i] = ( (uint64_t)rand() * 256 ) / RAND_MAX;
}
}
void E_( asf_HeaderParse ) ( asf_header_t *hdr,
uint8_t *p_header, int i_header )
{
var_buffer_t buffer;
guid_t guid;
uint64_t i_size;
int i;
for( i = 0; i < 128; i++ )
{
hdr->stream[i].i_cat = ASF_STREAM_UNKNOWN;
}
//fprintf( stderr, " ---------------------header:%d\n", i_header );
var_buffer_initread( &buffer, p_header, i_header );
var_buffer_getguid( &buffer, &guid );
if( !CmpGuid( &guid, &asf_object_header_guid ) )
{
// XXX Error
// fprintf( stderr, " ---------------------ERROR------\n" );
}
var_buffer_getmemory( &buffer, NULL, 30 - 16 );
for( ;; )
{
//fprintf( stderr, " ---------------------data:%d\n", buffer.i_data );
var_buffer_getguid( &buffer, &guid );
i_size = var_buffer_get64( &buffer );
//fprintf( stderr, " guid=0x%8.8x-0x%4.4x-0x%4.4x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x size=%lld\n",
// guid.v1,guid.v2, guid.v3,
// guid.v4[0],guid.v4[1],guid.v4[2],guid.v4[3],
// guid.v4[4],guid.v4[5],guid.v4[6],guid.v4[7],
// i_size );
if( CmpGuid( &guid, &asf_object_file_properties_guid ) )
{
var_buffer_getmemory( &buffer, NULL, 16 );
hdr->i_file_size = var_buffer_get64( &buffer );
var_buffer_getmemory( &buffer, NULL, 8 );
hdr->i_data_packets_count = var_buffer_get64( &buffer );
var_buffer_getmemory( &buffer, NULL, 8+8+8+4);
hdr->i_min_data_packet_size = var_buffer_get32( &buffer );
var_buffer_getmemory( &buffer, NULL, i_size - 24 - 16 - 8 - 8 - 8 - 8-8-8-4 - 4);
}
else if( CmpGuid( &guid, &asf_object_stream_properties_guid ) )
{
int i_stream_id;
guid_t stream_type;
//msg_Dbg( p_input, "found stream_properties" );
var_buffer_getguid( &buffer, &stream_type );
var_buffer_getmemory( &buffer, NULL, 32 );
i_stream_id = var_buffer_get8( &buffer ) & 0x7f;
//fprintf( stderr, " 1---------------------skip:%lld\n", i_size - 24 - 32 - 16 - 1 );
var_buffer_getmemory( &buffer, NULL, i_size - 24 - 32 - 16 - 1);
if( CmpGuid( &stream_type, &asf_object_stream_type_video ) )
{
//fprintf( stderr, "\nvideo stream[%d] found\n", i_stream_id );
//msg_Dbg( p_input, "video stream[%d] found", i_stream_id );
hdr->stream[i_stream_id].i_cat = ASF_STREAM_VIDEO;
}
else if( CmpGuid( &stream_type, &asf_object_stream_type_audio ) )
{
//fprintf( stderr, "\naudio stream[%d] found\n", i_stream_id );
//msg_Dbg( p_input, "audio stream[%d] found", i_stream_id );
hdr->stream[i_stream_id].i_cat = ASF_STREAM_AUDIO;
}
else
{
// msg_Dbg( p_input, "unknown stream[%d] found", i_stream_id );
hdr->stream[i_stream_id].i_cat = ASF_STREAM_UNKNOWN;
}
}
else if ( CmpGuid( &guid, &asf_object_bitrate_properties_guid ) )
{
int i_count;
uint8_t i_stream_id;
i_count = var_buffer_get16( &buffer );
i_size -= 2;
while( i_count > 0 )
{
i_stream_id = var_buffer_get16( &buffer )&0x7f;
hdr->stream[i_stream_id].i_bitrate = var_buffer_get32( &buffer );
i_count--;
i_size -= 6;
}
//fprintf( stderr, " 2---------------------skip:%lld\n", i_size - 24);
var_buffer_getmemory( &buffer, NULL, i_size - 24 );
}
else
{
//fprintf( stderr, " 3---------------------skip:%lld\n", i_size - 24);
// skip unknown guid
var_buffer_getmemory( &buffer, NULL, i_size - 24 );
}
if( var_buffer_readempty( &buffer ) )
{
return;
}
}
}
void E_( asf_StreamSelect ) ( asf_header_t *hdr,
int i_bitrate_max,
vlc_bool_t b_all, vlc_bool_t b_audio, vlc_bool_t b_video )
{
/* XXX FIXME use mututal eclusion information */
int i;
int i_audio, i_video;
int i_bitrate_total;
char *psz_stream;
i_audio = 0;
i_video = 0;
i_bitrate_total = 0;
if( b_all )
{
/* select all valid stream */
for( i = 1; i < 128; i++ )
{
if( hdr->stream[i].i_cat != ASF_STREAM_UNKNOWN )
{
hdr->stream[i].i_selected = 1;
}
}
return;
}
else
{
for( i = 0; i < 128; i++ )
{
hdr->stream[i].i_selected = 0; /* by default, not selected */
}
}
#if 0
psz_stream = config_GetPsz( p_input, "mms-stream" );
if( psz_stream && *psz_stream )
{
char *psz_tmp = psz_stream;
while( *psz_tmp )
{
if( *psz_tmp == ',' )
{
psz_tmp++;
}
else
{
int i_stream;
i_stream = atoi( psz_tmp );
while( *psz_tmp != '\0' && *psz_tmp != ',' )
{
psz_tmp++;
}
if( i_stream > 0 && i_stream < 128 &&
stream[i_stream].i_cat != MMS_STREAM_UNKNOWN )
{
stream[i_stream].i_selected = 1;
}
}
}
FREE( psz_stream );
return;
}
FREE( psz_stream );
#endif
/* big test:
* select a stream if
* - no audio nor video stream
* - or:
* - if i_bitrate_max not set keep the highest bitrate
* - if i_bitrate_max is set, keep stream that make we used best
* quality regarding i_bitrate_max
*
* XXX: little buggy:
* - it doesn't use mutual exclusion info..
* - when selecting a better stream we could select
* something that make i_bitrate_total> i_bitrate_max
*/
for( i = 1; i < 128; i++ )
{
if( hdr->stream[i].i_cat == ASF_STREAM_UNKNOWN )
{
continue;
}
else if( hdr->stream[i].i_cat == ASF_STREAM_AUDIO && b_audio &&
( i_audio <= 0 ||
( ( ( hdr->stream[i].i_bitrate > hdr->stream[i_audio].i_bitrate &&
( i_bitrate_total + hdr->stream[i].i_bitrate - hdr->stream[i_audio].i_bitrate
< i_bitrate_max || !i_bitrate_max) ) ||
( hdr->stream[i].i_bitrate < hdr->stream[i_audio].i_bitrate &&
i_bitrate_max != 0 && i_bitrate_total > i_bitrate_max )
) ) ) )
{
/* unselect old stream */
if( i_audio > 0 )
{
hdr->stream[i_audio].i_selected = 0;
if( hdr->stream[i_audio].i_bitrate> 0 )
{
i_bitrate_total -= hdr->stream[i_audio].i_bitrate;
}
}
hdr->stream[i].i_selected = 1;
if( hdr->stream[i].i_bitrate> 0 )
{
i_bitrate_total += hdr->stream[i].i_bitrate;
}
i_audio = i;
}
else if( hdr->stream[i].i_cat == ASF_STREAM_VIDEO && b_video &&
( i_video <= 0 ||
(
( ( hdr->stream[i].i_bitrate > hdr->stream[i_video].i_bitrate &&
( i_bitrate_total + hdr->stream[i].i_bitrate - hdr->stream[i_video].i_bitrate
< i_bitrate_max || !i_bitrate_max) ) ||
( hdr->stream[i].i_bitrate < hdr->stream[i_video].i_bitrate &&
i_bitrate_max != 0 && i_bitrate_total > i_bitrate_max )
) ) ) )
{
/* unselect old stream */
if( i_video > 0 )
{
hdr->stream[i_video].i_selected = 0;
if( hdr->stream[i_video].i_bitrate> 0 )
{
i_bitrate_total -= hdr->stream[i_video].i_bitrate;
}
}
hdr->stream[i].i_selected = 1;
if( hdr->stream[i].i_bitrate> 0 )
{
i_bitrate_total += hdr->stream[i].i_bitrate;
}
i_video = i;
}
}
#if 0
if( i_bitrate_max > 0 )
{
msg_Dbg( p_input,
"requested bitrate:%d real bitrate:%d",
i_bitrate_max, i_bitrate_total );
}
else
{
msg_Dbg( p_input,
"total bitrate:%d",
i_bitrate_total );
}
#endif
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* asf.h: MMS access plug-in * asf.h: MMS access plug-in
***************************************************************************** *****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN * Copyright (C) 2001, 2002 VideoLAN
* $Id: asf.h,v 1.4 2003/03/02 18:17:58 fenrir Exp $ * $Id: asf.h,v 1.5 2003/04/20 19:29:43 fenrir Exp $
* *
* Authors: Laurent Aimar <fenrir@via.ecp.fr> * Authors: Laurent Aimar <fenrir@via.ecp.fr>
* *
...@@ -28,6 +28,30 @@ ...@@ -28,6 +28,30 @@
* *
****************************************************************************/ ****************************************************************************/
#define ASF_STREAM_VIDEO 0x0001
#define ASF_STREAM_AUDIO 0x0002
#define ASF_STREAM_UNKNOWN 0xffff
typedef struct asf_stream_s
{
int i_id; /* 1 -> 127 */
int i_cat; /* ASF_STREAM_VIDEO, ASF_STREAM_AUDIO */
int i_bitrate; /* -1 if unknown */
int i_selected;
} asf_stream_t;
typedef struct
{
int64_t i_file_size;
int64_t i_data_packets_count;
int32_t i_min_data_packet_size;
asf_stream_t stream[128];
} asf_header_t;
typedef struct guid_s typedef struct guid_s
{ {
uint32_t v1; /* le */ uint32_t v1; /* le */
...@@ -36,36 +60,13 @@ typedef struct guid_s ...@@ -36,36 +60,13 @@ typedef struct guid_s
uint8_t v4[8]; uint8_t v4[8];
} guid_t; } guid_t;
static inline int CmpGuid( const guid_t *p_guid1, const guid_t *p_guid2 )
{ void E_( GenerateGuid ) ( guid_t * );
return( ( p_guid1->v1 == p_guid2->v1 && void E_( asf_HeaderParse ) ( asf_header_t *, uint8_t *, int );
p_guid1->v2 == p_guid2->v2 && void E_( asf_StreamSelect ) ( asf_header_t *,
p_guid1->v3 == p_guid2->v3 && int i_bitrate_max, vlc_bool_t b_all, vlc_bool_t b_audio,
p_guid1->v4[0] == p_guid2->v4[0] && vlc_bool_t b_video );
p_guid1->v4[1] == p_guid2->v4[1] &&
p_guid1->v4[2] == p_guid2->v4[2] &&
p_guid1->v4[3] == p_guid2->v4[3] &&
p_guid1->v4[4] == p_guid2->v4[4] &&
p_guid1->v4[5] == p_guid2->v4[5] &&
p_guid1->v4[6] == p_guid2->v4[6] &&
p_guid1->v4[7] == p_guid2->v4[7] ) ? 1 : 0 );
}
static void GenerateGuid( guid_t *p_guid )
{
int i;
srand( mdate() & 0xffffffff );
/* FIXME should be generated using random data */
p_guid->v1 = 0xbabac001;
p_guid->v2 = ( (uint64_t)rand() << 16 ) / RAND_MAX;
p_guid->v3 = ( (uint64_t)rand() << 16 ) / RAND_MAX;
for( i = 0; i < 8; i++ )
{
p_guid->v4[i] = ( (uint64_t)rand() * 256 ) / RAND_MAX;
}
}
#define GUID_FMT "%8.8x-%4.4x-%4.4x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x" #define GUID_FMT "%8.8x-%4.4x-%4.4x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x"
#define GUID_PRINT( guid ) \ #define GUID_PRINT( guid ) \
......
/***************************************************************************** /*****************************************************************************
* mms.c: MMS access plug-in * mms.c: MMS over tcp, udp and http access plug-in
***************************************************************************** *****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN * Copyright (C) 2001, 2002 VideoLAN
* $Id: mms.c,v 1.31 2003/03/30 18:14:35 gbazin Exp $ * $Id: mms.c,v 1.32 2003/04/20 19:29:43 fenrir Exp $
* *
* Authors: Laurent Aimar <fenrir@via.ecp.fr> * Authors: Laurent Aimar <fenrir@via.ecp.fr>
* *
...@@ -22,54 +22,16 @@ ...@@ -22,54 +22,16 @@
*****************************************************************************/ *****************************************************************************/
/*
* TODO:
* - clean code, break huge code block
* - fix memory leak
* - begin udp support...
*/
/***************************************************************************** /*****************************************************************************
* Preamble * Preamble
*****************************************************************************/ *****************************************************************************/
#include <stdlib.h> #include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <vlc/vlc.h> #include <vlc/vlc.h>
#include <vlc/input.h> #include <vlc/input.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
# ifndef IN_MULTICAST
# define IN_MULTICAST(a) IN_CLASSD(a)
# endif
#else
# include <sys/socket.h>
# include <netinet/in.h>
# if HAVE_ARPA_INET_H
# include <arpa/inet.h>
# elif defined( SYS_BEOS )
# include <net/netdb.h>
# endif
#endif
#include "network.h"
#include "asf.h"
#include "buffer.h"
#include "mms.h" #include "mms.h"
#undef MMS_DEBUG
/**************************************************************************** /****************************************************************************
* NOTES: * NOTES:
* MMSProtocole documentation found at http://get.to/sdp * MMSProtocole documentation found at http://get.to/sdp
...@@ -78,50 +40,16 @@ ...@@ -78,50 +40,16 @@
/***************************************************************************** /*****************************************************************************
* Local prototypes * Local prototypes
*****************************************************************************/ *****************************************************************************/
static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * );
static int Read ( input_thread_t * p_input, byte_t * p_buffer,
size_t i_len );
static void Seek ( input_thread_t *, off_t );
static int SetProgram ( input_thread_t *, pgrm_descriptor_t * );
static int MMSOpen( input_thread_t *, url_t *, int, char * );
static int MMSStart ( input_thread_t *, uint32_t );
static int MMSStop ( input_thread_t *p_input );
static int MMSClose ( input_thread_t * );
static int mms_CommandRead( input_thread_t *p_input, int i_command1, int i_command2 );
static int mms_CommandSend( input_thread_t *, int, uint32_t, uint32_t, uint8_t *, int );
static int mms_HeaderMediaRead( input_thread_t *, int );
static int mms_ReceivePacket( input_thread_t * );
static void mms_ParseURL( url_t *p_url, char *psz_url );
struct access_sys_t
{
int i_proto;
};
/* static int Open ( vlc_object_t * );
* XXX DON'T FREE MY MEMORY !!! XXX static void Close ( vlc_object_t * );
* non mais :P
*/
/*
* Ok, ok, j'le ferai plus...
*/
/*
* Merci :))
*/
/*
* Vous pourriez signer vos commentaires (mme si on voit bien qui peut
* crire ce genre de trucs :p), et crire en anglais, bordel de
* merde :p.
*/
/***************************************************************************** /*****************************************************************************
* Module descriptor * Module descriptor
...@@ -141,153 +69,54 @@ vlc_module_begin(); ...@@ -141,153 +69,54 @@ vlc_module_begin();
add_bool( "mms-all", 0, NULL, add_bool( "mms-all", 0, NULL,
"force selection of all streams", "force selection of all streams",
"force selection of all streams", VLC_TRUE ); "force selection of all streams", VLC_TRUE );
#if 0
add_string( "mms-stream", NULL, NULL, add_string( "mms-stream", NULL, NULL,
"streams selection", "streams selection",
"force this stream selection", VLC_TRUE ); "force this stream selection", VLC_TRUE );
#endif
add_integer( "mms-maxbitrate", 0, NULL, add_integer( "mms-maxbitrate", 0, NULL,
"max bitrate", "max bitrate",
"set max bitrate for auto streams selections", VLC_FALSE ); "set max bitrate for auto streams selections", VLC_FALSE );
add_shortcut( "mms" ); add_shortcut( "mms" );
add_shortcut( "mmsu" ); add_shortcut( "mmsu" );
add_shortcut( "mmst" ); add_shortcut( "mmst" );
add_shortcut( "mmsh" );
set_callbacks( Open, Close ); set_callbacks( Open, Close );
vlc_module_end(); vlc_module_end();
#define BUF_SIZE 200000
static int Open( vlc_object_t *p_this ) static int Open( vlc_object_t *p_this )
{ {
access_t *p_access;
int i_proto;
char *psz_network;
int i_status;
input_thread_t *p_input = (input_thread_t*)p_this; input_thread_t *p_input = (input_thread_t*)p_this;
/* *** allocate p_access_data *** */ int i_err;
p_input->p_access_data =
(void*)p_access = malloc( sizeof( access_t ) );
memset( p_access, 0, sizeof( access_t ) );
/* *** Parse URL and get server addr/port and path *** */
mms_ParseURL( &p_access->url, p_input->psz_name );
if( p_access->url.psz_server_addr == NULL ||
!( *p_access->url.psz_server_addr ) )
{
FREE( p_access->url.psz_private );
msg_Err( p_input, "invalid server name" );
return( -1 );
}
if( p_access->url.i_server_port == 0 )
{
p_access->url.i_server_port = 1755; /* default port */
}
if( p_access->url.i_bind_port == 0 )
{
p_access->url.i_bind_port = 7000; /* default port */
}
/* *** connect to this server *** */
/* 1: look at requested protocol (udp/tcp) */
i_proto = MMS_PROTO_AUTO;
if( *p_input->psz_access ) if( *p_input->psz_access )
{ {
if( !strncmp( p_input->psz_access, "mmsu", 4 ) ) if( !strncmp( p_input->psz_access, "mmsu", 4 ) )
{ {
i_proto = MMS_PROTO_UDP; return MMSTUOpen( p_input );
} }
else if( !strncmp( p_input->psz_access, "mmst", 4 ) ) else if( !strncmp( p_input->psz_access, "mmst", 4 ) )
{ {
i_proto = MMS_PROTO_TCP; return MMSTUOpen( p_input );
} }
} else if( !strncmp( p_input->psz_access, "mmsh", 4 ) )
/* 2: look at ip version ipv4/ipv6 */ {
psz_network = ""; return MMSHOpen( p_input );
if( config_GetInt( p_input, "ipv4" ) )
{
psz_network = "ipv4";
}
else if( config_GetInt( p_input, "ipv6" ) )
{
psz_network = "ipv6";
}
/* 3: connect */
if( i_proto == MMS_PROTO_AUTO )
{ /* first try with TCP */
i_status =
MMSOpen( p_input, &p_access->url, MMS_PROTO_TCP, psz_network );
if( i_status < 0 )
{ /* then with UDP */
i_status =
MMSOpen( p_input, &p_access->url, MMS_PROTO_UDP, psz_network );
} }
} }
else
{
i_status =
MMSOpen( p_input, &p_access->url, i_proto, psz_network );
}
if( i_status < 0 )
{
msg_Err( p_input, "cannot connect to server" );
FREE( p_access->url.psz_private );
return( -1 );
}
msg_Dbg( p_input, "connected to %s", p_access->url.psz_server_addr );
/* *** set exported functions *** */
p_input->pf_read = Read;
p_input->pf_seek = Seek;
p_input->pf_set_program = SetProgram;
p_input->pf_set_area = NULL;
p_input->p_private = NULL;
/* *** finished to set some variable *** */
vlc_mutex_lock( &p_input->stream.stream_lock );
p_input->stream.b_pace_control = 0;
p_input->stream.p_selected_area->i_tell = 0;
/*
* i_flags_broadcast
* yy xx ?? ??
* broadcast yy=0x02, xx= 0x00
* pre-recorded yy=0x01, xx= 0x80 if video, 0x00 no video
*/
if( p_access->i_packet_count <= 0 || ( p_access->i_flags_broadcast >> 24 ) == 0x02 )
{
p_input->stream.b_seekable = 0;
p_input->stream.p_selected_area->i_size = 0;
}
else
{
p_input->stream.b_seekable = 1;
p_input->stream.p_selected_area->i_size =
p_access->i_header +
p_access->i_packet_count * p_access->i_packet_length;
}
p_input->stream.i_method = INPUT_METHOD_NETWORK; i_err = MMSTUOpen( p_input );
vlc_mutex_unlock( &p_input->stream.stream_lock );
/* *** Start stream *** */ if( i_err )
if( MMSStart( p_input, 0xffffffff ) < 0 )
{ {
msg_Err( p_input, "cannot start stream" ); i_err = MMSHOpen( p_input );
MMSClose( p_input );
FREE( p_access->url.psz_private );
return( -1 );
} }
/* Update default_pts to a suitable value for mms access */ return i_err;
p_input->i_pts_delay = config_GetInt( p_input, "mms-caching" ) * 1000;
return( 0 );
} }
/***************************************************************************** /*****************************************************************************
...@@ -296,1668 +125,130 @@ static int Open( vlc_object_t *p_this ) ...@@ -296,1668 +125,130 @@ static int Open( vlc_object_t *p_this )
static void Close( vlc_object_t *p_this ) static void Close( vlc_object_t *p_this )
{ {
input_thread_t * p_input = (input_thread_t *)p_this; input_thread_t * p_input = (input_thread_t *)p_this;
access_t *p_access = (access_t*)p_input->p_access_data; access_sys_t * p_sys = p_input->p_access_data;
/* close connection with server */
MMSClose( p_input );
/* free memory */
FREE( p_access->url.psz_private );
FREE( p_input->p_access_data );
}
/*****************************************************************************
* SetProgram: do nothing
*****************************************************************************/
static int SetProgram( input_thread_t * p_input,
pgrm_descriptor_t * p_program )
{
return( 0 );
}
/*****************************************************************************
* Seek: try to go at the right place
*****************************************************************************/
static void Seek( input_thread_t * p_input, off_t i_pos )
{
access_t *p_access = (access_t*)p_input->p_access_data;
uint32_t i_packet;
uint32_t i_offset;
var_buffer_t buffer;
if( i_pos < 0 )
{
return;
}
vlc_mutex_lock( &p_input->stream.stream_lock );
if( i_pos < p_access->i_header)
{
if( p_access->i_pos < p_access->i_header )
{
/* no need to restart stream, it was already one
* or no stream was yet read */
p_access->i_pos = i_pos;
return;
}
else
{
i_packet = 0xffffffff;
i_offset = 0;
}
}
else
{
i_packet = ( i_pos - p_access->i_header ) / p_access->i_packet_length;
i_offset = ( i_pos - p_access->i_header ) % p_access->i_packet_length;
}
msg_Dbg( p_input, "seeking to "I64Fd " (packet:%d)", i_pos, i_packet );
MMSStop( p_input );
msg_Dbg( p_input, "stream stopped (seek)" );
/* *** restart stream *** */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add64( &buffer, 0 ); /* seek point in second */
var_buffer_add32( &buffer, 0xffffffff );
var_buffer_add32( &buffer, i_packet ); // begin from start
var_buffer_add8( &buffer, 0xff ); // stream time limit
var_buffer_add8( &buffer, 0xff ); // on 3bytes ...
var_buffer_add8( &buffer, 0xff ); //
var_buffer_add8( &buffer, 0x00 ); // don't use limit
var_buffer_add32( &buffer, p_access->i_media_packet_id_type );
mms_CommandSend( p_input, 0x07, p_access->i_command_level, 0x0001ffff,
buffer.p_data, buffer.i_data );
var_buffer_free( &buffer );
for( ;; ) if( p_sys->i_proto == MMS_PROTO_TCP || p_sys->i_proto == MMS_PROTO_UDP )
{ {
mms_HeaderMediaRead( p_input, MMS_PACKET_CMD ); MMSTUClose( p_input );
if( p_access->i_command == 0x1e )
{
msg_Dbg( p_input, "received 0x1e (seek)" );
break;
}
} }
else if( p_sys->i_proto == MMS_PROTO_HTTP )
for( ;; )
{ {
mms_HeaderMediaRead( p_input, MMS_PACKET_CMD ); MMSHClose( p_input );
if( p_access->i_command == 0x05 )
{
msg_Dbg( p_input, "received 0x05 (seek)" );
break;
}
} }
/* get a packet */
mms_HeaderMediaRead( p_input, MMS_PACKET_MEDIA );
msg_Dbg( p_input, "Streaming restarted" );
p_access->i_media_used += i_offset;
p_access->i_pos = i_pos;
p_input->stream.p_selected_area->i_tell = i_pos;
vlc_mutex_unlock( &p_input->stream.stream_lock );
} }
static int Read ( input_thread_t * p_input, byte_t * p_buffer, /****************************************************************************
size_t i_len ) * parse hostname:port/path@username:password
* FIXME ipv6 ip will be baddly parsed (contain ':' )
****************************************************************************/
url_t *E_( url_new ) ( char * psz_url )
{ {
access_t *p_access = (access_t*)p_input->p_access_data; url_t *p_url = malloc( sizeof( url_t ) );
size_t i_data;
size_t i_copy;
i_data = 0; char *psz_dup = strdup( psz_url );
char *psz_parser = psz_dup;
/* *** send header if needed ** */ char *psz_tmp;
if( p_access->i_pos < p_access->i_header )
{
i_copy = __MIN( i_len, p_access->i_header - p_access->i_pos );
if( i_copy > 0 )
{
memcpy( p_buffer,
p_access->p_header + p_access->i_pos,
i_copy );
}
i_data += i_copy;
}
/* *** now send data if needed *** */ /* 1: get hostname:port */
while( i_data < i_len ) while( *psz_parser == '/' )
{ {
if( p_access->i_media_used < p_access->i_media ) psz_parser++;
{
i_copy = __MIN( i_len - i_data ,
p_access->i_media - p_access->i_media_used );
memcpy( p_buffer + i_data,
p_access->p_media + p_access->i_media_used,
i_copy );
i_data += i_copy;
p_access->i_media_used += i_copy;
}
else if( p_access->p_media != NULL &&
p_access->i_media_used < p_access->i_packet_length )
{
i_copy = __MIN( i_len - i_data,
p_access->i_packet_length - p_access->i_media_used);
memset( p_buffer + i_data, 0, i_copy );
i_data += i_copy;
p_access->i_media_used += i_copy;
}
else
{
if( p_access->i_eos
|| mms_HeaderMediaRead( p_input, MMS_PACKET_MEDIA ) < 0 )
{
p_access->i_pos += i_data;
return( i_data );
}
}
} }
p_access->i_pos += i_data; psz_tmp = psz_parser;
return( i_data );
}
static void asf_HeaderParse( mms_stream_t stream[128],
uint8_t *p_header, int i_header )
{
var_buffer_t buffer;
guid_t guid;
uint64_t i_size;
int i;
for( i = 0; i < 128; i++ ) while( *psz_parser &&
*psz_parser != ':' && *psz_parser != '/' && *psz_parser != '@' )
{ {
stream[i].i_cat = MMS_STREAM_UNKNOWN; psz_parser++;
} }
//fprintf( stderr, " ---------------------header:%d\n", i_header ); p_url->psz_host = strndup( psz_tmp, psz_parser - psz_tmp );
var_buffer_initread( &buffer, p_header, i_header );
var_buffer_getguid( &buffer, &guid );
if( !CmpGuid( &guid, &asf_object_header_guid ) )
{
// XXX Error
// fprintf( stderr, " ---------------------ERROR------\n" );
}
var_buffer_getmemory( &buffer, NULL, 30 - 16 );
for( ;; ) if( *psz_parser == ':' )
{ {
//fprintf( stderr, " ---------------------data:%d\n", buffer.i_data ); psz_parser++;
psz_tmp = psz_parser;
var_buffer_getguid( &buffer, &guid );
i_size = var_buffer_get64( &buffer );
//fprintf( stderr, " guid=0x%8.8x-0x%4.4x-0x%4.4x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x size=%lld\n",
// guid.v1,guid.v2, guid.v3,
// guid.v4[0],guid.v4[1],guid.v4[2],guid.v4[3],
// guid.v4[4],guid.v4[5],guid.v4[6],guid.v4[7],
// i_size );
if( CmpGuid( &guid, &asf_object_stream_properties_guid ) )
{
int i_stream_id;
guid_t stream_type;
//msg_Dbg( p_input, "found stream_properties" );
var_buffer_getguid( &buffer, &stream_type );
var_buffer_getmemory( &buffer, NULL, 32 );
i_stream_id = var_buffer_get8( &buffer ) & 0x7f;
//fprintf( stderr, " 1---------------------skip:%lld\n", i_size - 24 - 32 - 16 - 1 );
var_buffer_getmemory( &buffer, NULL, i_size - 24 - 32 - 16 - 1);
if( CmpGuid( &stream_type, &asf_object_stream_type_video ) )
{
//fprintf( stderr, "\nvideo stream[%d] found\n", i_stream_id );
//msg_Dbg( p_input, "video stream[%d] found", i_stream_id );
stream[i_stream_id].i_cat = MMS_STREAM_VIDEO;
}
else if( CmpGuid( &stream_type, &asf_object_stream_type_audio ) )
{
//fprintf( stderr, "\naudio stream[%d] found\n", i_stream_id );
//msg_Dbg( p_input, "audio stream[%d] found", i_stream_id );
stream[i_stream_id].i_cat = MMS_STREAM_AUDIO;
}
else
{
// msg_Dbg( p_input, "unknown stream[%d] found", i_stream_id );
stream[i_stream_id].i_cat = MMS_STREAM_UNKNOWN;
}
}
else if ( CmpGuid( &guid, &asf_object_bitrate_properties_guid ) )
{
int i_count;
uint8_t i_stream_id;
i_count = var_buffer_get16( &buffer );
i_size -= 2;
while( i_count > 0 )
{
i_stream_id = var_buffer_get16( &buffer )&0x7f;
stream[i_stream_id].i_bitrate = var_buffer_get32( &buffer );
i_count--;
i_size -= 6;
}
//fprintf( stderr, " 2---------------------skip:%lld\n", i_size - 24);
var_buffer_getmemory( &buffer, NULL, i_size - 24 );
}
else
{
//fprintf( stderr, " 3---------------------skip:%lld\n", i_size - 24);
// skip unknown guid
var_buffer_getmemory( &buffer, NULL, i_size - 24 );
}
if( var_buffer_readempty( &buffer ) )
{
return;
}
}
}
static void mms_StreamSelect( input_thread_t * p_input,
mms_stream_t stream[128] )
{
/* XXX FIXME use mututal eclusion information */
int i;
int i_audio, i_video;
int b_audio, b_video;
int i_bitrate_total;
int i_bitrate_max;
char *psz_stream;
i_audio = 0; while( *psz_parser && *psz_parser != '/' && *psz_parser != '@' )
i_video = 0;
i_bitrate_total = 0;
i_bitrate_max = config_GetInt( p_input, "mms-maxbitrate" );
b_audio = config_GetInt( p_input, "audio" );
b_video = config_GetInt( p_input, "video" );
if( config_GetInt( p_input, "mms-all" ) )
{
/* select all valid stream */
for( i = 1; i < 128; i++ )
{ {
if( stream[i].i_cat != MMS_STREAM_UNKNOWN ) psz_parser++;
{
stream[i].i_selected = 1;
}
} }
return; p_url->i_port = atoi( psz_tmp );
} }
else else
{ {
for( i = 0; i < 128; i++ ) p_url->i_port = 0;
{
stream[i].i_selected = 0; /* by default, not selected */
}
} }
psz_stream = config_GetPsz( p_input, "mms-stream" );
if( psz_stream && *psz_stream ) /* 2: get path */
if( *psz_parser == '/' )
{ {
char *psz_tmp = psz_stream; //psz_parser++;
while( *psz_tmp )
{
if( *psz_tmp == ',' )
{
psz_tmp++;
}
else
{
int i_stream;
i_stream = atoi( psz_tmp );
while( *psz_tmp != '\0' && *psz_tmp != ',' )
{
psz_tmp++;
}
if( i_stream > 0 && i_stream < 128 &&
stream[i_stream].i_cat != MMS_STREAM_UNKNOWN )
{
stream[i_stream].i_selected = 1;
}
}
}
FREE( psz_stream );
return;
}
FREE( psz_stream );
/* big test: psz_tmp = psz_parser;
* select a stream if
* - no audio nor video stream
* - or:
* - if i_bitrate_max not set keep the highest bitrate
* - if i_bitrate_max is set, keep stream that make we used best
* quality regarding i_bitrate_max
*
* XXX: little buggy:
* - it doesn't use mutual exclusion info..
* - when selecting a better stream we could select
* something that make i_bitrate_total> i_bitrate_max
*/
for( i = 1; i < 128; i++ )
{
if( stream[i].i_cat == MMS_STREAM_UNKNOWN )
{
continue;
}
else if( stream[i].i_cat == MMS_STREAM_AUDIO && b_audio &&
( i_audio <= 0 ||
( ( ( stream[i].i_bitrate > stream[i_audio].i_bitrate &&
( i_bitrate_total + stream[i].i_bitrate - stream[i_audio].i_bitrate
< i_bitrate_max || !i_bitrate_max) ) ||
( stream[i].i_bitrate < stream[i_audio].i_bitrate &&
i_bitrate_max != 0 && i_bitrate_total > i_bitrate_max )
) ) ) )
{
/* unselect old stream */
if( i_audio > 0 )
{
stream[i_audio].i_selected = 0;
if( stream[i_audio].i_bitrate> 0 )
{
i_bitrate_total -= stream[i_audio].i_bitrate;
}
}
stream[i].i_selected = 1; while( *psz_parser && *psz_parser != '@' )
if( stream[i].i_bitrate> 0 )
{
i_bitrate_total += stream[i].i_bitrate;
}
i_audio = i;
}
else if( stream[i].i_cat == MMS_STREAM_VIDEO && b_video &&
( i_video <= 0 ||
(
( ( stream[i].i_bitrate > stream[i_video].i_bitrate &&
( i_bitrate_total + stream[i].i_bitrate - stream[i_video].i_bitrate
< i_bitrate_max || !i_bitrate_max) ) ||
( stream[i].i_bitrate < stream[i_video].i_bitrate &&
i_bitrate_max != 0 && i_bitrate_total > i_bitrate_max )
) ) ) )
{ {
/* unselect old stream */ psz_parser++;
stream[i_video].i_selected = 0;
if( stream[i_video].i_bitrate> 0 )
{
i_bitrate_total -= stream[i_video].i_bitrate;
}
stream[i].i_selected = 1;
if( stream[i].i_bitrate> 0 )
{
i_bitrate_total += stream[i].i_bitrate;
}
i_video = i;
} }
} p_url->psz_path = strndup( psz_tmp, psz_parser - psz_tmp );
if( i_bitrate_max > 0 )
{
msg_Dbg( p_input,
"requested bitrate:%d real bitrate:%d",
i_bitrate_max, i_bitrate_total );
} }
else else
{ {
msg_Dbg( p_input, p_url->psz_path = strdup( "" );
"total bitrate:%d",
i_bitrate_total );
}
}
/****************************************************************************
* MMSOpen : Open a connection with the server over mmst or mmsu(not yet)
****************************************************************************/
static int MMSOpen( input_thread_t *p_input,
url_t *p_url,
int i_proto,
char *psz_network ) /* "", "ipv4", "ipv6" */
{
module_t *p_network;
access_t *p_access = (access_t*)p_input->p_access_data;
network_socket_t socket_desc;
int b_udp = ( i_proto == MMS_PROTO_UDP ) ? 1 : 0;
var_buffer_t buffer;
char tmp[4096];
uint16_t *p;
int i_server_version;
int i_tool_version;
int i_update_player_url;
int i_encryption_type;
int i;
int i_streams;
int i_first;
/* *** Open a TCP connection with server *** */
msg_Dbg( p_input, "waiting for connection..." );
socket_desc.i_type = NETWORK_TCP;
socket_desc.psz_server_addr = p_url->psz_server_addr;
socket_desc.i_server_port = p_url->i_server_port;
socket_desc.psz_bind_addr = "";
socket_desc.i_bind_port = 0;
p_input->p_private = (void*)&socket_desc;
if( !( p_network = module_Need( p_input, "network", psz_network ) ) )
{
msg_Err( p_input, "failed to open a connection (tcp)" );
return( -1 );
} }
module_Unneed( p_input, p_network );
p_access->socket_tcp.i_handle = socket_desc.i_handle;
p_input->i_mtu = 0; /*socket_desc.i_mtu;*/
msg_Dbg( p_input,
"connection(tcp) with \"%s:%d\" successful",
p_url->psz_server_addr,
p_url->i_server_port );
/* *** Bind port if UDP protocol is selected *** */ /* 3: usrname and password */
if( b_udp ) if( *psz_parser == '@' )
{ {
if( !p_url->psz_bind_addr || !*p_url->psz_bind_addr ) psz_parser++;
{
struct sockaddr_in name;
socklen_t i_namelen = sizeof( struct sockaddr_in );
if( getsockname( p_access->socket_tcp.i_handle, psz_tmp = psz_parser;
(struct sockaddr*)&name, &i_namelen ) < 0 )
{
msg_Err( p_input, "for udp you have to provide bind address (mms://<server_addr>@<bind_addr/<path> (FIXME)" ); while( *psz_parser && *psz_parser != ':' )
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_access->socket_tcp.i_handle );
#elif defined( WIN32 )
closesocket( p_access->socket_tcp.i_handle );
#else
close( p_access->socket_tcp.i_handle );
#endif
return( -1 );
}
p_access->psz_bind_addr = inet_ntoa( name.sin_addr );
}
else
{ {
p_access->psz_bind_addr = strdup( p_url->psz_bind_addr ); psz_parser++;
}
socket_desc.i_type = NETWORK_UDP;
socket_desc.psz_server_addr = "";
socket_desc.i_server_port = 0;
socket_desc.psz_bind_addr = p_access->psz_bind_addr;
socket_desc.i_bind_port = p_url->i_bind_port;
p_input->p_private = (void*)&socket_desc;
if( !( p_network = module_Need( p_input, "network", psz_network ) ) )
{
msg_Err( p_input, "failed to open a connection (udp)" );
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_access->socket_tcp.i_handle );
#elif defined( WIN32 )
closesocket( p_access->socket_tcp.i_handle );
#else
close( p_access->socket_tcp.i_handle );
#endif
return( -1 );
} }
module_Unneed( p_input, p_network );
p_access->socket_udp.i_handle = socket_desc.i_handle;
p_input->i_mtu = 0;/*socket_desc.i_mtu; FIXME */
}
else
{
p_access->psz_bind_addr = NULL;
}
/* *** Init context for mms prototcol *** */
GenerateGuid( &p_access->guid ); /* used to identify client by server */
msg_Dbg( p_input,
"generated guid: "GUID_FMT,
GUID_PRINT( p_access->guid ) );
p_access->i_command_level = 1; /* updated after 0x1A command */
p_access->i_seq_num = 0;
p_access->i_media_packet_id_type = 0x04;
p_access->i_header_packet_id_type = 0x02;
p_access->i_proto = i_proto;
p_access->i_packet_seq_num = 0;
p_access->p_header = NULL;
p_access->i_header = 0;
p_access->p_media = NULL;
p_access->i_media = 0;
p_access->i_media_used = 0;
p_access->i_pos = 0;
p_access->i_buffer_tcp = 0;
p_access->i_buffer_udp = 0;
p_access->p_cmd = NULL;
p_access->i_cmd = 0;
p_access->i_eos = 0;
/* *** send command 1 : connection request *** */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add16( &buffer, 0x001c );
var_buffer_add16( &buffer, 0x0003 );
sprintf( tmp,
"NSPlayer/7.0.0.1956; {"GUID_FMT"}; Host: %s",
GUID_PRINT( p_access->guid ),
p_url->psz_server_addr );
var_buffer_addUTF16( &buffer, tmp );
mms_CommandSend( p_input, p_url->psz_username = strndup( psz_tmp, psz_parser - psz_tmp );
0x01, /* connexion request */
0x00000000, /* flags, FIXME */
0x0004000b, /* ???? */
buffer.p_data,
buffer.i_data );
if( mms_CommandRead( p_input, 0x01, 0 ) < 0 ) if( *psz_parser == ':' )
{
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
i_server_version = GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 32 );
i_tool_version = GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 36 );
i_update_player_url = GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 40 );
i_encryption_type = GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 44 );
p = (uint16_t*)( p_access->p_cmd + MMS_CMD_HEADERSIZE + 48 );
#define GETUTF16( psz, size ) \
{ \
int i; \
psz = malloc( size + 1); \
for( i = 0; i < size; i++ ) \
{ \
psz[i] = p[i]; \
} \
psz[size] = '\0'; \
p += 2 * ( size ); \
}
GETUTF16( p_access->psz_server_version, i_server_version );
GETUTF16( p_access->psz_tool_version, i_tool_version );
GETUTF16( p_access->psz_update_player_url, i_update_player_url );
GETUTF16( p_access->psz_encryption_type, i_encryption_type );
#undef GETUTF16
msg_Dbg( p_input,
"0x01 --> server_version:\"%s\" tool_version:\"%s\" update_player_url:\"%s\" encryption_type:\"%s\"",
p_access->psz_server_version,
p_access->psz_tool_version,
p_access->psz_update_player_url,
p_access->psz_encryption_type );
/* *** should make an 18 command to make data timing *** */
/* *** send command 2 : transport protocol selection *** */
var_buffer_reinitwrite( &buffer, 0 );
var_buffer_add32( &buffer, 0x00000000 );
var_buffer_add32( &buffer, 0x000a0000 );
var_buffer_add32( &buffer, 0x00000002 );
if( b_udp )
{
sprintf( tmp,
"\\\\%s\\UDP\\%d",
p_access->psz_bind_addr,
p_url->i_bind_port );
}
else
{
sprintf( tmp, "\\\\192.168.0.1\\TCP\\1242" );
}
var_buffer_addUTF16( &buffer, tmp );
var_buffer_add16( &buffer, '0' );
mms_CommandSend( p_input,
0x02, /* connexion request */
0x00000000, /* flags, FIXME */
0xffffffff, /* ???? */
buffer.p_data,
buffer.i_data );
/* *** response from server, should be 0x02 or 0x03 *** */
mms_CommandRead( p_input, 0x02, 0x03 );
if( p_access->i_command == 0x03 )
{
msg_Err( p_input,
"%s protocol selection failed", b_udp ? "UDP" : "TCP" );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
else if( p_access->i_command != 0x02 )
{
msg_Warn( p_input, "received command isn't 0x02 in reponse to 0x02" );
}
/* *** send command 5 : media file name/path requested *** */
var_buffer_reinitwrite( &buffer, 0 );
var_buffer_add64( &buffer, 0 );
var_buffer_addUTF16( &buffer, p_url->psz_path );
mms_CommandSend( p_input,
0x05,
p_access->i_command_level,
0xffffffff,
buffer.p_data,
buffer.i_data );
/* *** wait for reponse *** */
mms_CommandRead( p_input, 0x1a, 0x06 );
/* test if server send 0x1A answer */
if( p_access->i_command == 0x1A )
{
msg_Err( p_input, "id/password requested (not yet supported)" );
/* FIXME */
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
if( p_access->i_command != 0x06 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x06)",
p_access->i_command );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
/* 1 for file ok, 2 for authen ok */
switch( GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE ) )
{
case 0x0001:
msg_Dbg( p_input, "Media file name/path accepted" );
break;
case 0x0002:
msg_Dbg( p_input, "Authentication accepted" );
break;
case -1:
default:
msg_Err( p_input, "error while asking for file %d",
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE ) );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
p_access->i_flags_broadcast =
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 12 );
p_access->i_media_length =
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 24 );
p_access->i_packet_length =
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 44 );
p_access->i_packet_count =
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 48 );
p_access->i_max_bit_rate =
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 56 );
p_access->i_header_size =
GetDWLE( p_access->p_cmd + MMS_CMD_HEADERSIZE + 60 );
msg_Dbg( p_input,
"answer 0x06 flags:0x%8.8x media_length:%ds packet_length:%d packet_count:%d max_bit_rate:%d header_size:%d",
p_access->i_flags_broadcast,
p_access->i_media_length,
p_access->i_packet_length,
p_access->i_packet_count,
p_access->i_max_bit_rate,
p_access->i_header_size );
/* XXX XXX dirty hack XXX XXX */
p_input->i_mtu = 3 * p_access->i_packet_length;
/* *** send command 15 *** */
var_buffer_reinitwrite( &buffer, 0 );
var_buffer_add32( &buffer, 0 );
var_buffer_add32( &buffer, 0x8000 );
var_buffer_add32( &buffer, 0xffffffff );
var_buffer_add32( &buffer, 0x00 );
var_buffer_add32( &buffer, 0x00 );
var_buffer_add32( &buffer, 0x00 );
var_buffer_add64( &buffer, 0x40ac200000000000 );
var_buffer_add32( &buffer, p_access->i_header_packet_id_type );
mms_CommandSend( p_input, 0x15, p_access->i_command_level, 0x00,
buffer.p_data, buffer.i_data );
/* *** wait for reponse *** */
mms_CommandRead( p_input, 0x11, 0 );
if( p_access->i_command != 0x11 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x11)",
p_access->i_command );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
/* *** now read header packet *** */
/* XXX could be split over multiples packets */
for( ;; )
{
if( mms_HeaderMediaRead( p_input, MMS_PACKET_HEADER ) < 0 )
{
msg_Err( p_input, "cannot receive header" );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
if( p_access->i_header >= p_access->i_header_size )
{ {
msg_Dbg( p_input, psz_parser++;
"header complete(%d)",
p_access->i_header );
break;
}
msg_Dbg( p_input,
"header incomplete (%d/%d), reading more",
p_access->i_header,
p_access->i_header_size );
}
/* *** parse header and get stream and their id *** */
/* get all streams properties,
*
* TODO : stream bitrates properties(optional)
* and bitrate mutual exclusion(optional) */
asf_HeaderParse( p_access->stream,
p_access->p_header, p_access->i_header );
mms_StreamSelect( p_input, p_access->stream );
/* *** now select stream we want to receive *** */
/* TODO take care of stream bitrate TODO */
i_streams = 0;
i_first = -1;
var_buffer_reinitwrite( &buffer, 0 );
/* for now, select first audio and video stream */
for( i = 1; i < 128; i++ )
{
if( p_access->stream[i].i_cat != MMS_STREAM_UNKNOWN ) p_url->psz_password = strdup( psz_parser );
}
else
{ {
i_streams++; p_url->psz_password = strdup( "" );
if( i_first != -1 )
{
var_buffer_add16( &buffer, 0xffff );
var_buffer_add16( &buffer, i );
}
else
{
i_first = i;
}
if( p_access->stream[i].i_selected )
{
var_buffer_add16( &buffer, 0x0000 );
msg_Info( p_input,
"selecting stream[0x%x] %s (%d kb/s)",
i,
( p_access->stream[i].i_cat == MMS_STREAM_AUDIO ) ?
"audio" : "video" ,
p_access->stream[i].i_bitrate / 1024);
}
else
{
var_buffer_add16( &buffer, 0x0002 );
msg_Info( p_input,
"ignoring stream[0x%x] %s (%d kb/s)",
i,
( p_access->stream[i].i_cat == MMS_STREAM_AUDIO ) ?
"audio" : "video" ,
p_access->stream[i].i_bitrate / 1024);
}
} }
} }
else
if( i_streams == 0 )
{
msg_Err( p_input, "cannot find any stream" );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
mms_CommandSend( p_input, 0x33,
i_streams,
0xffff | ( i_first << 16 ),
buffer.p_data, buffer.i_data );
mms_CommandRead( p_input, 0x21, 0 );
if( p_access->i_command != 0x21 )
{ {
msg_Err( p_input, p_url->psz_username = strdup( "" );
"unknown answer (0x%x instead of 0x21)", p_url->psz_password = strdup( "" );
p_access->i_command );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
} }
#if 0
fprintf( stderr,
var_buffer_free( &buffer ); "host=`%s' port=%d path=`%s' username=`%s' password=`%s'\n",
p_url->psz_host,
msg_Info( p_input, "connection sucessful" ); p_url->i_port,
p_url->psz_path,
return( 0 ); p_url->psz_username,
p_url->psz_password );
#endif
free( psz_dup );
return p_url;
} }
/**************************************************************************** void E_( url_free ) ( url_t * p_url )
* MMSStart : Start streaming
****************************************************************************/
static int MMSStart ( input_thread_t *p_input, uint32_t i_packet )
{ {
access_t *p_access = (access_t*)p_input->p_access_data; free( p_url->psz_host );
var_buffer_t buffer; free( p_url->psz_path );
free( p_url->psz_username );
/* *** start stream from packet 0 *** */ free( p_url->psz_password );
var_buffer_initwrite( &buffer, 0 ); free( p_url );
var_buffer_add64( &buffer, 0 ); /* seek point in second */
var_buffer_add32( &buffer, 0xffffffff );
var_buffer_add32( &buffer, i_packet ); // begin from start
var_buffer_add8( &buffer, 0xff ); // stream time limit
var_buffer_add8( &buffer, 0xff ); // on 3bytes ...
var_buffer_add8( &buffer, 0xff ); //
var_buffer_add8( &buffer, 0x00 ); // don't use limit
var_buffer_add32( &buffer, p_access->i_media_packet_id_type );
mms_CommandSend( p_input, 0x07, p_access->i_command_level, 0x0001ffff,
buffer.p_data, buffer.i_data );
var_buffer_free( &buffer );
mms_CommandRead( p_input, 0x05, 0 );
if( p_access->i_command != 0x05 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x05)",
p_access->i_command );
return( -1 );
}
else
{
/* get a packet */
mms_HeaderMediaRead( p_input, MMS_PACKET_MEDIA );
msg_Dbg( p_input, "Streaming started" );
return( 0 );
}
}
/****************************************************************************
* MMSStop : Stop streaming
****************************************************************************/
static int MMSStop ( input_thread_t *p_input )
{
access_t *p_access = (access_t*)p_input->p_access_data;
/* *** stop stream but keep connection alive *** */
mms_CommandSend( p_input,
0x09,
p_access->i_command_level,
0x001fffff,
NULL, 0 );
return( 0 );
}
/****************************************************************************
* MMSClose : Close streaming and connection
****************************************************************************/
static int MMSClose ( input_thread_t *p_input )
{
access_t *p_access = (access_t*)p_input->p_access_data;
msg_Dbg( p_input, "Connection closed" );
/* *** tell server that we will disconnect *** */
mms_CommandSend( p_input,
0x0d,
p_access->i_command_level,
0x00000001,
NULL, 0 );
/* *** close sockets *** */
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_access->socket_tcp.i_handle );
#elif defined( WIN32 )
closesocket( p_access->socket_tcp.i_handle );
#else
close( p_access->socket_tcp.i_handle );
#endif
if( p_access->i_proto == MMS_PROTO_UDP )
{
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_access->socket_udp.i_handle );
#elif defined( WIN32 )
closesocket( p_access->socket_udp.i_handle );
#else
close( p_access->socket_udp.i_handle );
#endif
}
FREE( p_access->p_cmd );
FREE( p_access->p_media );
FREE( p_access->p_header );
FREE( p_access->psz_server_version );
FREE( p_access->psz_tool_version );
FREE( p_access->psz_update_player_url );
FREE( p_access->psz_encryption_type );
return( 0 );
}
/*****************************************************************************
* mms_ParseURL : parse an url string and fill an url_t
*****************************************************************************/
static void mms_ParseURL( url_t *p_url, char *psz_url )
{
char *psz_parser;
char *psz_server_port;
p_url->psz_private = strdup( psz_url );
psz_parser = p_url->psz_private;
while( *psz_parser == '/' )
{
psz_parser++;
}
p_url->psz_server_addr = psz_parser;
while( *psz_parser &&
*psz_parser != ':' && *psz_parser != '/' && *psz_parser != '@' )
{
psz_parser++;
}
if( *psz_parser == ':' )
{
*psz_parser = '\0';
psz_parser++;
psz_server_port = psz_parser;
while( *psz_parser && *psz_parser != '/' )
{
psz_parser++;
}
}
else
{
psz_server_port = "";
}
if( *psz_parser == '@' )
{
char *psz_bind_port;
*psz_parser = '\0';
psz_parser++;
p_url->psz_bind_addr = psz_parser;
while( *psz_parser && *psz_parser != ':' && *psz_parser != '/' )
{
psz_parser++;
}
if( *psz_parser == ':' )
{
*psz_parser = '\0';
psz_parser++;
psz_bind_port = psz_parser;
while( *psz_parser && *psz_parser != '/' )
{
psz_parser++;
}
}
else
{
psz_bind_port = "";
}
if( *psz_bind_port )
{
p_url->i_bind_port = strtol( psz_bind_port, &psz_parser, 10 );
}
else
{
p_url->i_bind_port = 0;
}
}
else
{
p_url->psz_bind_addr = "";
p_url->i_bind_port = 0;
}
if( *psz_parser == '/' )
{
*psz_parser = '\0';
psz_parser++;
p_url->psz_path = psz_parser;
}
if( *psz_server_port )
{
p_url->i_server_port = strtol( psz_server_port, &psz_parser, 10 );
}
else
{
p_url->i_server_port = 0;
}
}
/****************************************************************************
*
* MMS specific functions
*
****************************************************************************/
static int mms_CommandSend( input_thread_t *p_input,
int i_command,
uint32_t i_prefix1, uint32_t i_prefix2,
uint8_t *p_data, int i_data )
{
var_buffer_t buffer;
access_t *p_access = (access_t*)p_input->p_access_data;
int i_data_by8;
i_data_by8 = ( i_data + 7 ) / 8;
/* first init uffer */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add32( &buffer, 0x00000001 ); /* start sequence */
var_buffer_add32( &buffer, 0xB00BFACE );
/* size after protocol type */
var_buffer_add32( &buffer, i_data + MMS_CMD_HEADERSIZE - 16 );
var_buffer_add32( &buffer, 0x20534d4d ); /* protocol "MMS " */
var_buffer_add32( &buffer, i_data_by8 + 4 );
var_buffer_add32( &buffer, p_access->i_seq_num ); p_access->i_seq_num++;
var_buffer_add64( &buffer, 0 );
var_buffer_add32( &buffer, i_data_by8 + 2 );
var_buffer_add32( &buffer, 0x00030000 | i_command ); /* dir | command */
var_buffer_add32( &buffer, i_prefix1 ); /* command specific */
var_buffer_add32( &buffer, i_prefix2 ); /* command specific */
/* specific command data */
if( p_data && i_data > 0 )
{
var_buffer_addmemory( &buffer, p_data, i_data );
}
/* send it */
if( send( p_access->socket_tcp.i_handle,
buffer.p_data,
buffer.i_data,
0 ) == -1 )
{
msg_Err( p_input, "failed to send command" );
return( -1 );
}
var_buffer_free( &buffer );
return( 0 );
}
static int NetFillBuffer( input_thread_t *p_input )
{
#ifdef UNDER_CE
return -1;
#else
access_t *p_access = (access_t*)p_input->p_access_data;
struct timeval timeout;
fd_set fds;
int i_ret;
/* FIXME when using udp */
ssize_t i_tcp, i_udp;
ssize_t i_tcp_read, i_udp_read;
int i_handle_max;
/* Initialize file descriptor set */
FD_ZERO( &fds );
i_tcp = MMS_BUFFER_SIZE/2 - p_access->i_buffer_tcp;
if( p_access->i_proto == MMS_PROTO_UDP )
{
i_udp = MMS_BUFFER_SIZE/2 - p_access->i_buffer_udp;
}
else
{
i_udp = 0; /* there isn't udp socket */
}
i_handle_max = 0;
if( i_tcp > 0 )
{
FD_SET( p_access->socket_tcp.i_handle, &fds );
i_handle_max = __MAX( i_handle_max, p_access->socket_tcp.i_handle );
}
if( i_udp > 0 )
{
FD_SET( p_access->socket_udp.i_handle, &fds );
i_handle_max = __MAX( i_handle_max, p_access->socket_udp.i_handle );
}
if( i_handle_max == 0 )
{
msg_Warn( p_input, "nothing to read %d:%d", i_tcp, i_udp );
return( 0 );
}
else
{
// msg_Warn( p_input, "ask for tcp:%d udp:%d", i_tcp, i_udp );
}
/* We'll wait 0.5 second if nothing happens */
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
/* Find if some data is available */
while( (i_ret = select( i_handle_max + 1, &fds,
NULL, NULL, &timeout )) == 0
|| (i_ret < 0 && errno == EINTR) )
{
FD_ZERO( &fds );
if( i_tcp > 0 ) FD_SET( p_access->socket_tcp.i_handle, &fds );
if( i_udp > 0 ) FD_SET( p_access->socket_udp.i_handle, &fds );
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
if( p_input->b_die || p_input->b_error )
{
return 0;
}
}
if( i_ret < 0 )
{
msg_Err( p_input, "network select error (%s)", strerror(errno) );
return -1;
}
if( i_tcp > 0 && FD_ISSET( p_access->socket_tcp.i_handle, &fds ) )
{
i_tcp_read =
recv( p_access->socket_tcp.i_handle,
p_access->buffer_tcp + p_access->i_buffer_tcp,
i_tcp + MMS_BUFFER_SIZE/2, 0 );
}
else
{
i_tcp_read = 0;
}
if( i_udp > 0 && FD_ISSET( p_access->socket_udp.i_handle, &fds ) )
{
i_udp_read = recv( p_access->socket_udp.i_handle,
p_access->buffer_udp + p_access->i_buffer_udp,
i_udp + MMS_BUFFER_SIZE/2, 0 );
}
else
{
i_udp_read = 0;
}
#if MMS_DEBUG
if( p_access->i_proto == MMS_PROTO_UDP )
{
msg_Dbg( p_input,
"filling buffer TCP:%d+%d UDP:%d+%d",
p_access->i_buffer_tcp,
i_tcp_read,
p_access->i_buffer_udp,
i_udp_read );
}
else
{
msg_Dbg( p_input,
"filling buffer TCP:%d+%d",
p_access->i_buffer_tcp,
i_tcp_read );
}
#endif
if( i_tcp_read > 0 )
{
p_access->i_buffer_tcp += i_tcp_read;
}
if( i_udp_read > 0 )
{
p_access->i_buffer_udp += i_udp_read;
}
return( i_tcp_read + i_udp_read);
#endif
}
static int mms_ParseCommand( input_thread_t *p_input,
uint8_t *p_data,
int i_data,
int *pi_used )
{
#define GET32( i_pos ) \
( p_access->p_cmd[i_pos] + ( p_access->p_cmd[i_pos +1] << 8 ) + \
( p_access->p_cmd[i_pos + 2] << 16 ) + \
( p_access->p_cmd[i_pos + 3] << 24 ) )
access_t *p_access = (access_t*)p_input->p_access_data;
int i_length;
uint32_t i_id;
if( p_access->p_cmd )
{
free( p_access->p_cmd );
}
p_access->i_cmd = i_data;
p_access->p_cmd = malloc( i_data );
memcpy( p_access->p_cmd, p_data, i_data );
*pi_used = i_data; /* by default */
if( i_data < MMS_CMD_HEADERSIZE )
{
msg_Warn( p_input, "truncated command (header incomplete)" );
p_access->i_command = 0;
return( -1 );
}
i_id = GetDWLE( p_data + 4 );
i_length = GetDWLE( p_data + 8 ) + 16;
if( i_id != 0xb00bface )
{
msg_Err( p_input,
"incorrect command header (0x%x)", i_id );
p_access->i_command = 0;
return( -1 );
}
if( i_length > p_access->i_cmd )
{
msg_Warn( p_input,
"truncated command (missing %d bytes)",
i_length - i_data );
p_access->i_command = 0;
return( -1 );
}
else if( i_length < p_access->i_cmd )
{
p_access->i_cmd = i_length;
*pi_used = i_length;
}
msg_Dbg( p_input,
"recv command start_sequence:0x%8.8x command_id:0x%8.8x length:%d len8:%d sequence 0x%8.8x len8_II:%d dir_comm:0x%8.8x",
GET32( 0 ),
GET32( 4 ),
GET32( 8 ),
/* 12: protocol type "MMS " */
GET32( 16 ),
GET32( 20 ),
/* 24: unknown (0) */
/* 28: unknown (0) */
GET32( 32 ),
GET32( 36 )
/* 40: switches */
/* 44: extra */ );
p_access->i_command = GET32( 36 ) & 0xffff;
return( MMS_PACKET_CMD );
}
static int mms_ParsePacket( input_thread_t *p_input,
uint8_t *p_data, size_t i_data,
int *pi_used )
{
access_t *p_access = (access_t*)p_input->p_access_data;
int i_packet_seq_num;
size_t i_packet_length;
uint32_t i_packet_id;
uint8_t *p_packet;
*pi_used = i_data; /* default */
if( i_data <= 8 )
{
msg_Warn( p_input, "truncated packet (header incomplete)" );
return( -1 );
}
i_packet_id = p_data[4];
i_packet_seq_num = GetDWLE( p_data );
i_packet_length = GetWLE( p_data + 6 );
//msg_Warn( p_input, "------->i_packet_length=%d, i_data=%d", i_packet_length, i_data );
if( i_packet_length > i_data || i_packet_length <= 8)
{
msg_Dbg( p_input,
"truncated packet (missing %d bytes)",
i_packet_length - i_data );
*pi_used = 0;
return( -1 );
}
else if( i_packet_length < i_data )
{
*pi_used = i_packet_length;
}
if( i_packet_id == 0xff )
{
msg_Warn( p_input,
"receive MMS UDP pair timing" );
return( MMS_PACKET_UDP_TIMING );
}
if( i_packet_id != p_access->i_header_packet_id_type &&
i_packet_id != p_access->i_media_packet_id_type )
{
msg_Warn( p_input, "incorrect Packet Id Type (0x%x)", i_packet_id );
return( -1 );
}
/* we now have a media or a header packet */
p_packet = malloc( i_packet_length - 8 ); // don't bother with preheader
memcpy( p_packet, p_data + 8, i_packet_length - 8 );
if( i_packet_seq_num != p_access->i_packet_seq_num )
{
/* FIXME for udp could be just wrong order ? */
msg_Warn( p_input,
"detected packet lost (%d != %d)",
i_packet_seq_num,
p_access->i_packet_seq_num );
p_access->i_packet_seq_num = i_packet_seq_num;
}
p_access->i_packet_seq_num++;
if( i_packet_id == p_access->i_header_packet_id_type )
{
if( p_access->p_header )
{
p_access->p_header = realloc( p_access->p_header,
p_access->i_header + i_packet_length - 8 );
memcpy( &p_access->p_header[p_access->i_header],
p_packet,
i_packet_length - 8 );
p_access->i_header += i_packet_length - 8;
free( p_packet );
}
else
{
p_access->p_header = p_packet;
p_access->i_header = i_packet_length - 8;
}
/* msg_Dbg( p_input,
"receive header packet (%d bytes)",
i_packet_length - 8 ); */
return( MMS_PACKET_HEADER );
}
else
{
FREE( p_access->p_media );
p_access->p_media = p_packet;
p_access->i_media = i_packet_length - 8;
p_access->i_media_used = 0;
/* msg_Dbg( p_input,
"receive media packet (%d bytes)",
i_packet_length - 8 ); */
return( MMS_PACKET_MEDIA );
}
}
static int mms_ReceivePacket( input_thread_t *p_input )
{
access_t *p_access = (access_t*)p_input->p_access_data;
int i_packet_tcp_type;
int i_packet_udp_type;
for( ;; )
{
if( NetFillBuffer( p_input ) < 0 )
{
msg_Warn( p_input, "cannot fill buffer" );
continue;
}
i_packet_tcp_type = -1;
i_packet_udp_type = -1;
if( p_access->i_buffer_tcp > 0 )
{
int i_used;
if( GetDWLE( p_access->buffer_tcp + 4 ) == 0xb00bface )
{
i_packet_tcp_type =
mms_ParseCommand( p_input,
p_access->buffer_tcp,
p_access->i_buffer_tcp,
&i_used );
}
else
{
i_packet_tcp_type =
mms_ParsePacket( p_input,
p_access->buffer_tcp,
p_access->i_buffer_tcp,
&i_used );
}
if( i_used > 0 && i_used < MMS_BUFFER_SIZE )
{
memmove( p_access->buffer_tcp,
p_access->buffer_tcp + i_used,
MMS_BUFFER_SIZE - i_used );
}
p_access->i_buffer_tcp -= i_used;
}
else if( p_access->i_buffer_udp > 0 )
{
int i_used;
i_packet_udp_type =
mms_ParsePacket( p_input,
p_access->buffer_udp,
p_access->i_buffer_udp,
&i_used );
if( i_used > 0 && i_used < MMS_BUFFER_SIZE )
{
memmove( p_access->buffer_udp,
p_access->buffer_udp + i_used,
MMS_BUFFER_SIZE - i_used );
}
p_access->i_buffer_udp -= i_used;
}
if( i_packet_tcp_type == MMS_PACKET_CMD &&
p_access->i_command == 0x1b )
{
mms_CommandSend( p_input, 0x1b, 0, 0, NULL, 0 );
i_packet_tcp_type = -1;
}
if( i_packet_tcp_type != -1 )
{
return( i_packet_tcp_type );
}
else if( i_packet_udp_type != -1 )
{
return( i_packet_udp_type );
}
}
}
static int mms_ReceiveCommand( input_thread_t *p_input )
{
access_t *p_access = (access_t*)p_input->p_access_data;
for( ;; )
{
int i_used;
int i_status;
if( NetFillBuffer( p_input ) < 0 )
{
msg_Warn( p_input, "cannot fill buffer" );
continue;
}
if( p_access->i_buffer_tcp > 0 )
{
i_status = mms_ParseCommand( p_input,
p_access->buffer_tcp,
p_access->i_buffer_tcp,
&i_used );
if( i_used < MMS_BUFFER_SIZE )
{
memmove( p_access->buffer_tcp,
p_access->buffer_tcp + i_used,
MMS_BUFFER_SIZE - i_used );
}
p_access->i_buffer_tcp -= i_used;
if( i_status < 0 )
{
return( -1 );
}
if( p_access->i_command == 0x1b )
{
mms_CommandSend( p_input, 0x1b, 0, 0, NULL, 0 );
}
else
{
break;
}
}
else
{
return( -1 );
}
}
return( 0 );
} }
#define MMS_RETRY_MAX 10
#define MMS_RETRY_SLEEP 50000
static int mms_CommandRead( input_thread_t *p_input, int i_command1, int i_command2 )
{
access_t *p_access = (access_t*)p_input->p_access_data;
int i_count;
int i_status;
for( i_count = 0; i_count < MMS_RETRY_MAX; )
{
i_status = mms_ReceiveCommand( p_input );
if( i_status < 0 || p_access->i_command == 0 )
{
i_count++;
msleep( MMS_RETRY_SLEEP );
}
else if( i_command1 == 0 && i_command2 == 0)
{
return( 0 );
}
else if( p_access->i_command == i_command1 || p_access->i_command == i_command2 )
{
return( 0 );
}
else
{
switch( p_access->i_command )
{
case 0x03:
msg_Warn( p_input, "socket closed by server" );
p_access->i_eos = 1;
return( -1 );
case 0x1e:
msg_Warn( p_input, "end of media stream" );
p_access->i_eos = 1;
return( -1 );
default:
break;
}
}
}
msg_Warn( p_input, "failed to receive command (abording)" );
return( -1 );
}
static int mms_HeaderMediaRead( input_thread_t *p_input, int i_type )
{
access_t *p_access = (access_t*)p_input->p_access_data;
int i_count;
for( i_count = 0; i_count < MMS_RETRY_MAX; )
{
int i_status;
i_status = mms_ReceivePacket( p_input );
if( i_status < 0 )
{
i_count++;
msg_Warn( p_input,
"cannot receive header (%d/%d)", i_count, MMS_RETRY_MAX );
msleep( MMS_RETRY_SLEEP );
}
else if( i_status == i_type || i_type == MMS_PACKET_ANY )
{
return( i_type );
}
else if( i_status == MMS_PACKET_CMD )
{
switch( p_access->i_command )
{
case 0x03:
msg_Warn( p_input, "socket closed by server" );
p_access->i_eos = 1;
return( -1 );
case 0x1e:
msg_Warn( p_input, "end of media stream" );
p_access->i_eos = 1;
return( -1 );
case 0x20:
/* XXX not too dificult to be done EXCEPT that we
* need to restart demuxer... and I don't see how we
* could do that :p */
msg_Err( p_input,
"reinitialization needed --> unsupported" );
p_access->i_eos = 1;
return( -1 );
default:
break;
}
}
}
msg_Err( p_input,
"cannot receive %s (abording)",
( i_type == MMS_PACKET_HEADER ) ? "header" : "media data" );
return( -1 );
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* mms.h: MMS access plug-in * mms.h: MMS access plug-in
***************************************************************************** *****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN * Copyright (C) 2001, 2002 VideoLAN
* $Id: mms.h,v 1.8 2003/03/03 01:38:07 fenrir Exp $ * $Id: mms.h,v 1.9 2003/04/20 19:29:43 fenrir Exp $
* *
* Authors: Laurent Aimar <fenrir@via.ecp.fr> * Authors: Laurent Aimar <fenrir@via.ecp.fr>
* *
...@@ -21,112 +21,19 @@ ...@@ -21,112 +21,19 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/ *****************************************************************************/
/* url: [/]host[:port][/path] */
typedef struct url_s
{
char *psz_server_addr;
int i_server_port;
char *psz_bind_addr;
int i_bind_port;
char *psz_path;
/* private */
char *psz_private;
} url_t;
#define FREE( p ) if( p ) { free( p ); (p ) = NULL; }
#define MMS_PROTO_AUTO 0 #define MMS_PROTO_AUTO 0
#define MMS_PROTO_TCP 1 #define MMS_PROTO_TCP 1
#define MMS_PROTO_UDP 2 #define MMS_PROTO_UDP 2
#define MMS_PROTO_HTTP 3
#define MMS_PACKET_ANY 0
#define MMS_PACKET_CMD 1
#define MMS_PACKET_HEADER 2
#define MMS_PACKET_MEDIA 3
#define MMS_PACKET_UDP_TIMING 4
#define MMS_STREAM_VIDEO 0x0001
#define MMS_STREAM_AUDIO 0x0002
#define MMS_STREAM_UNKNOWN 0xffff
#define MMS_CMD_HEADERSIZE 48
typedef struct mms_stream_s
{
int i_id; /* 1 -> 127 */
int i_cat; /* MMS_STREAM_VIDEO, MMS_STREAM_AUDIO */
int i_bitrate; /* -1 if unknown */
int i_selected;
} mms_stream_t;
#define MMS_BUFFER_SIZE 100000
typedef struct access_s
{
int i_proto; /* MMS_PROTO_TCP, MMS_PROTO_UDP */
input_socket_t socket_tcp; /* TCP socket for communication with server */
input_socket_t socket_udp; /* Optional UDP socket for data(media/header packet) */
/* send by server */
char *psz_bind_addr; /* used by udp */
url_t url; /* connect to this server */
mms_stream_t stream[128]; /* in asf never more than 1->127 streams */
off_t i_pos; /* position of next byte to be read */ /* mmst and mmsu */
int E_( MMSTUOpen ) ( input_thread_t * );
/* */ void E_( MMSTUClose ) ( input_thread_t * );
uint8_t buffer_tcp[MMS_BUFFER_SIZE];
int i_buffer_tcp;
uint8_t buffer_udp[MMS_BUFFER_SIZE];
int i_buffer_udp;
/* data necessary to send data to server */
guid_t guid;
int i_command_level;
int i_seq_num;
uint32_t i_header_packet_id_type;
uint32_t i_media_packet_id_type;
int i_packet_seq_num;
uint8_t *p_cmd; /* latest command read */
int i_cmd; /* allocated at the begining */
uint8_t *p_header; /* allocated by mms_ReadPacket */
int i_header;
uint8_t *p_media; /* allocated by mms_ReadPacket */
size_t i_media;
size_t i_media_used;
/* extracted informations */
int i_command;
int i_eos;
/* from 0x01 answer (not yet set) */
char *psz_server_version;
char *psz_tool_version;
char *psz_update_player_url;
char *psz_encryption_type;
/* from 0x06 answer */
uint32_t i_flags_broadcast;
uint32_t i_media_length;
size_t i_packet_length;
uint32_t i_packet_count;
int i_max_bit_rate;
int i_header_size;
} access_t;
/* mmsh */
int E_( MMSHOpen ) ( input_thread_t * );
void E_( MMSHClose ) ( input_thread_t * );
static inline uint16_t GetWLE( uint8_t *p_buff ) static inline uint16_t GetWLE( uint8_t *p_buff )
{ {
...@@ -139,3 +46,20 @@ static inline uint32_t GetDWLE( uint8_t *p_buff ) ...@@ -139,3 +46,20 @@ static inline uint32_t GetDWLE( uint8_t *p_buff )
( p_buff[2] <<16 ) + ( p_buff[3] <<24 ) ); ( p_buff[2] <<16 ) + ( p_buff[3] <<24 ) );
} }
#define FREE( p ) if( p ) { free( p ); (p) = NULL; }
/* url: [/]host[:port][/path][@username[:password]] */
typedef struct url_s
{
char *psz_host;
int i_port;
char *psz_path;
char *psz_username;
char *psz_password;
} url_t;
url_t *E_( url_new ) ( char * );
void E_( url_free ) ( url_t * );
/*****************************************************************************
* mmsh.c:
*****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN
* $Id: mmsh.c,v 1.1 2003/04/20 19:29:43 fenrir Exp $
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*
* TODO:
* * http_proxy
*
*/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <vlc/vlc.h>
#include <vlc/input.h>
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#if defined( UNDER_CE )
# include <winsock.h>
#elif defined( WIN32 )
# include <winsock2.h>
# include <ws2tcpip.h>
# ifndef IN_MULTICAST
# define IN_MULTICAST(a) IN_CLASSD(a)
# endif
#else
# include <sys/socket.h>
#endif
#include "network.h"
#include "asf.h"
#include "buffer.h"
#include "mms.h"
#include "mmsh.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
int E_( MMSHOpen ) ( input_thread_t * );
void E_( MMSHClose ) ( input_thread_t * );
static int Read ( input_thread_t * p_input, byte_t * p_buffer,
size_t i_len );
static void Seek ( input_thread_t *, off_t );
/****************************************************************************
****************************************************************************
******************* *******************
******************* Main functions *******************
******************* *******************
****************************************************************************
****************************************************************************/
/****************************************************************************
* Open: connect to ftp server and ask for file
****************************************************************************/
int E_( MMSHOpen ) ( input_thread_t *p_input )
{
access_sys_t *p_sys;
uint8_t *p;
http_answer_t *p_ans;
http_field_t *p_field;
chunk_t ck;
/* init p_sys */
p_sys = malloc( sizeof( access_sys_t ) );
p_sys->i_proto = MMS_PROTO_HTTP;
p_sys->p_socket = NULL;
p_sys->i_request_context = 1;
p_sys->i_buffer = 0;
p_sys->i_buffer_pos = 0;
p_sys->b_broadcast = VLC_TRUE;
p_sys->p_packet = NULL;
p_sys->i_packet_sequence = 0;
p_sys->i_packet_used = 0;
p_sys->i_packet_length = 0;
p_sys->i_pos = 0;
E_( GenerateGuid )( &p_sys->guid );
/* open a tcp connection */
p_sys->p_url = E_( url_new )( p_input->psz_name );
if( *p_sys->p_url->psz_host == '\0' )
{
msg_Err( p_input, "invalid server addresse" );
goto exit_error;
}
if( p_sys->p_url->i_port <= 0 )
{
p_sys->p_url->i_port = 80;
}
p_sys->p_socket = NetOpenTCP( p_input, p_sys->p_url );
if( !p_sys->p_socket )
{
msg_Err( p_input, "cannot connect" );
goto exit_error;
}
p_sys->i_request_context = 1;
/* *** send first request *** */
/* FIXME use generateguid */
p = &p_sys->buffer[0];
p += sprintf( p, "GET %s HTTP/1.0\r\n", p_sys->p_url->psz_path );
p += sprintf( p,"Accept: */*\r\n" );
p += sprintf( p, "User-Agent: NSPlayer/4.1.0.3856\r\n" );
p += sprintf( p, "Host: %s:%d\r\n", p_sys->p_url->psz_host, p_sys->p_url->i_port );
p += sprintf( p, "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%d,max-duration=0\r\n", p_sys->i_request_context++ );
//p += sprintf( p, "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" );
p += sprintf( p, "Pragma: xClientGUID={"GUID_FMT"}\r\n", GUID_PRINT( p_sys->guid ) );
p += sprintf( p, "Connection: Close\r\n\r\n" );
NetWrite( p_input, p_sys->p_socket, p_sys->buffer, p - p_sys->buffer );
if( NetFill ( p_input, p_sys, BUFFER_SIZE ) <= 0 )
{
msg_Err( p_input, "cannot read answer" );
goto exit_error;
}
NetClose( p_input, p_sys->p_socket );
p_ans = http_answer_parse( p_sys->buffer, p_sys->i_buffer );
if( !p_ans )
{
msg_Err( p_input, "cannot parse answer" );
goto exit_error;
}
if( p_ans->i_error >= 400 )
{
msg_Err( p_input, "error %d (server return=`%s')", p_ans->i_error, p_ans->psz_answer );
http_answer_free( p_ans );
goto exit_error;
}
else if( p_ans->i_error >= 300 )
{
msg_Err( p_input, "FIXME redirec unsuported %d (server return=`%s')", p_ans->i_error, p_ans->psz_answer );
http_answer_free( p_ans );
goto exit_error;
}
else if( p_ans->i_body <= 0 )
{
msg_Err( p_input, "empty answer" );
http_answer_free( p_ans );
goto exit_error;
}
/* now get features */
/* FIXME FIXME test Content-Type to see if it's a plain stream or an asx FIXME */
for( p_field = p_ans->p_fields; p_field != NULL; p_field = http_field_find( p_field->p_next, "Pragma" ) )
{
if( !strncasecmp( p_field->psz_value, "features", 8 ) )
{
if( strstr( p_field->psz_value, "broadcast" ) )
{
msg_Dbg( p_input, "stream type = broadcast" );
p_sys->b_broadcast = VLC_TRUE;
}
else if( strstr( p_field->psz_value, "seekable" ) )
{
msg_Dbg( p_input, "stream type = seekable" );
p_sys->b_broadcast = VLC_FALSE;
}
else
{
msg_Warn( p_input, "unknow stream types (%s)", p_field->psz_value );
p_sys->b_broadcast = VLC_FALSE;
}
}
}
/* gather header */
p_sys->i_header = 0;
p_sys->p_header = malloc( p_ans->i_body );
do
{
if( chunk_parse( &ck, p_ans->p_body, p_ans->i_body ) )
{
msg_Err( p_input, "invalid chunk answer" );
goto exit_error;
}
if( ck.i_type != 0x4824 )
{
msg_Err( p_input, "invalid chunk (0x%x)", ck.i_type );
break;
}
if( ck.i_data > 0 )
{
memcpy( &p_sys->p_header[p_sys->i_header],
ck.p_data,
ck.i_data );
p_sys->i_header += ck.i_data;
}
/* BEURK */
p_ans->p_body += 12 + ck.i_data;
p_ans->i_body -= 12 + ck.i_data;
} while( p_ans->i_body > 12 );
http_answer_free( p_ans );
msg_Dbg( p_input, "complete header size=%d", p_sys->i_header );
if( p_sys->i_header <= 0 )
{
msg_Err( p_input, "header size == 0" );
goto exit_error;
}
/* *** parse header and get stream and their id *** */
/* get all streams properties,
*
* TODO : stream bitrates properties(optional)
* and bitrate mutual exclusion(optional) */
E_( asf_HeaderParse )( &p_sys->asfh,
p_sys->p_header, p_sys->i_header );
msg_Dbg( p_input, "packet count=%lld packet size=%d",p_sys->asfh.i_data_packets_count, p_sys->asfh.i_min_data_packet_size );
E_( asf_StreamSelect)( &p_sys->asfh,
config_GetInt( p_input, "mms-maxbitrate" ),
config_GetInt( p_input, "mms-all" ),
config_GetInt( p_input, "audio" ),
config_GetInt( p_input, "video" ) );
if( mmsh_start( p_input, p_sys, 0 ) )
{
msg_Err( p_input, "cannot start stream" );
goto exit_error;
}
/* *** set exported functions *** */
p_input->pf_read = Read;
p_input->pf_seek = Seek;
p_input->pf_set_program = input_SetProgram;
p_input->pf_set_area = NULL;
p_input->p_private = NULL;
//p_input->i_mtu = 5 * p_sys->asfh.i_min_data_packet_size;
/* *** finished to set some variable *** */
vlc_mutex_lock( &p_input->stream.stream_lock );
p_input->stream.b_pace_control = 1;
if( p_sys->b_broadcast )
{
p_input->stream.p_selected_area->i_size = 0;
p_input->stream.b_seekable = 0;
}
else
{
p_input->stream.p_selected_area->i_size = p_sys->asfh.i_file_size;
p_input->stream.b_seekable = 1;
}
p_input->stream.p_selected_area->i_tell = 0;
p_input->stream.i_method = INPUT_METHOD_NETWORK;
vlc_mutex_unlock( &p_input->stream.stream_lock );
/* Update default_pts to a suitable value for ftp access */
p_input->i_pts_delay = config_GetInt( p_input, "mms-caching" ) * 1000;
p_input->p_access_data = p_sys;
return( VLC_SUCCESS );
exit_error:
E_( url_free )( p_sys->p_url );
if( p_sys->p_socket )
{
NetClose( p_input, p_sys->p_socket );
}
free( p_sys );
return( VLC_EGENERIC );
}
/*****************************************************************************
* Close: free unused data structures
*****************************************************************************/
void E_( MMSHClose ) ( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
msg_Dbg( p_input, "stopping stream" );
mmsh_stop( p_input, p_sys );
free( p_sys );
}
static int mmsh_get_packet( input_thread_t * p_input, access_sys_t *p_sys, chunk_t *p_ck )
{
int i_mov = p_sys->i_buffer - p_sys->i_buffer_pos;
if( p_sys->i_buffer_pos > BUFFER_SIZE / 2 )
{
if( i_mov > 0 )
{
memmove( &p_sys->buffer[0],
&p_sys->buffer[p_sys->i_buffer_pos],
i_mov );
}
p_sys->i_buffer = i_mov;
p_sys->i_buffer_pos = 0;
}
if( NetFill( p_input, p_sys, 12 ) < 12 )
{
msg_Warn( p_input, "cannot fill buffer" );
return VLC_EGENERIC;
}
chunk_parse( p_ck, &p_sys->buffer[p_sys->i_buffer_pos], p_sys->i_buffer - p_sys->i_buffer_pos );
if( p_ck->i_type == 0x4524 ) // Transfer complete
{
msg_Warn( p_input, "EOF" );
return VLC_EGENERIC;
}
else if( p_ck->i_type != 0x4824 && p_ck->i_type != 0x4424 )
{
msg_Err( p_input, "invalid chunk FATAL" );
return VLC_EGENERIC;
}
if( p_ck->i_data < p_ck->i_size2 - 8 )
{
if( NetFill( p_input, p_sys, p_ck->i_size2 - 8 - p_ck->i_data ) <= 0 )
{
msg_Warn( p_input, "cannot fill buffer" );
return VLC_EGENERIC;
}
chunk_parse( p_ck, &p_sys->buffer[p_sys->i_buffer_pos], p_sys->i_buffer - p_sys->i_buffer_pos );
}
if( p_sys->i_packet_sequence != 0 && p_ck->i_sequence != p_sys->i_packet_sequence )
{
msg_Warn( p_input, "packet lost ?" );
}
p_sys->i_packet_sequence = p_ck->i_sequence + 1;
p_sys->i_packet_used = 0;
p_sys->i_packet_length = p_ck->i_data;
p_sys->p_packet = p_ck->p_data;
p_sys->i_buffer_pos += 12 + p_ck->i_data;
return VLC_SUCCESS;
}
/*****************************************************************************
* Seek: try to go at the right place
*****************************************************************************/
static void Seek( input_thread_t * p_input, off_t i_pos )
{
access_sys_t *p_sys = p_input->p_access_data;
chunk_t ck;
off_t i_offset;
off_t i_packet;
i_packet = ( i_pos - p_sys->i_header ) / p_sys->asfh.i_min_data_packet_size;
i_offset = ( i_pos - p_sys->i_header ) % p_sys->asfh.i_min_data_packet_size;
msg_Err( p_input, "seeking to "I64Fd, i_pos );
vlc_mutex_lock( &p_input->stream.stream_lock );
mmsh_stop( p_input, p_sys );
mmsh_start( p_input, p_sys, i_packet * p_sys->asfh.i_min_data_packet_size );
for( ;; )
{
if( mmsh_get_packet( p_input, p_sys, &ck ) )
{
break;
}
/* skip headers */
if( ck.i_type != 0x4824 )
{
break;
}
msg_Warn( p_input, "skipping header" );
}
p_sys->i_pos = i_pos;
p_sys->i_packet_used += i_offset;
p_input->stream.p_selected_area->i_tell = i_pos;
vlc_mutex_unlock( &p_input->stream.stream_lock );
}
/*****************************************************************************
* Read:
*****************************************************************************/
static int Read ( input_thread_t * p_input, byte_t * p_buffer,
size_t i_len )
{
access_sys_t *p_sys = p_input->p_access_data;
size_t i_copy;
size_t i_data = 0;
while( i_data < i_len )
{
if( p_sys->i_packet_used < p_sys->i_packet_length )
{
i_copy = __MIN( p_sys->i_packet_length - p_sys->i_packet_used, i_len - i_data );
memcpy( &p_buffer[i_data],
&p_sys->p_packet[p_sys->i_packet_used],
i_copy );
i_data += i_copy;
p_sys->i_packet_used += i_copy;
}
else if( p_sys->i_pos + i_data > p_sys->i_header &&
(int)p_sys->i_packet_used < p_sys->asfh.i_min_data_packet_size )
{
i_copy = __MIN( p_sys->asfh.i_min_data_packet_size - p_sys->i_packet_used, i_len - i_data );
memset( &p_buffer[i_data], 0, i_copy );
i_data += i_copy;
p_sys->i_packet_used += i_copy;
}
else
{
chunk_t ck;
/* get a new packet */
/* fill enought data (>12) */
msg_Dbg( p_input, "waiting data (buffer = %d bytes", p_sys->i_buffer );
if( mmsh_get_packet( p_input, p_sys, &ck ) )
{
return 0;
}
}
}
p_sys->i_pos += i_data;
return( i_data );
}
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
static int mmsh_start( input_thread_t *p_input, access_sys_t *p_sys, off_t i_pos )
{
uint8_t *p;
int i_streams = 0;
int i;
http_answer_t *p_ans;
msg_Dbg( p_input, "starting stream" );
p_sys->p_socket = NetOpenTCP( p_input, p_sys->p_url );
for( i = 1; i < 128; i++ )
{
if( p_sys->asfh.stream[i].i_selected )
{
i_streams++;
}
}
if( i_streams <= 0 )
{
msg_Err( p_input, "no stream selected" );
return VLC_EGENERIC;
}
p = &p_sys->buffer[0];
p += sprintf( p, "GET %s HTTP/1.0\r\n", p_sys->p_url->psz_path );
p += sprintf( p,"Accept: */*\r\n" );
p += sprintf( p, "User-Agent: NSPlayer/4.1.0.3856\r\n" );
p += sprintf( p, "Host: %s:%d\r\n", p_sys->p_url->psz_host, p_sys->p_url->i_port );
if( p_sys->b_broadcast )
{
p += sprintf( p, "Pragma: no-cache,rate=1.000000,request-context=%d\r\n", p_sys->i_request_context++ );
}
else
{
p += sprintf( p, "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=%u:%u,request-context=%d,max-duration=0\r\n",
(uint32_t)((i_pos >> 32)&0xffffffff), (uint32_t)(i_pos&0xffffffff), p_sys->i_request_context++ );
}
p += sprintf( p, "Pragma: xPlayStrm=1\r\n" );
//p += sprintf( p, "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" );
p += sprintf( p, "Pragma: xClientGUID={"GUID_FMT"}\r\n", GUID_PRINT( p_sys->guid ) );
p += sprintf( p, "Pragma: stream-switch-count=%d\r\n", i_streams );
p += sprintf( p, "Pragma: stream-switch-entry=" );
for( i = 0; i < i_streams; i++ )
{
if( p_sys->asfh.stream[i].i_selected )
{
p += sprintf( p, "ffff:%d:0 ", p_sys->asfh.stream[i].i_id );
}
else
{
p += sprintf( p, "ffff:%d:2 ", p_sys->asfh.stream[i].i_id );
}
}
p += sprintf( p, "\r\n" );
p += sprintf( p, "Connection: Close\r\n\r\n" );
NetWrite( p_input, p_sys->p_socket, p_sys->buffer, p - p_sys->buffer );
msg_Dbg( p_input, "filling buffer" );
/* we read until we found a \r\n\r\n or \n\n */
p_sys->i_buffer = 0;
p_sys->i_buffer_pos = 0;
for( ;; )
{
int i_try = 0;
int i_read;
uint8_t *p;
p = &p_sys->buffer[p_sys->i_buffer];
i_read =
NetRead( p_input, p_sys->p_socket,
&p_sys->buffer[p_sys->i_buffer],
1024 );
if( i_read == 0 )
{
if( i_try++ > 12 )
{
break;
}
msg_Dbg( p_input, "another try (%d/12)", i_try );
continue;
}
if( i_read <= 0 || p_input->b_die || p_input->b_error )
{
break;
}
p_sys->i_buffer += i_read;
p_sys->buffer[p_sys->i_buffer] = '\0';
if( strstr( p, "\r\n\r\n" ) || strstr( p, "\n\n" ) )
{
msg_Dbg( p_input, "body found" );
break;
}
if( p_sys->i_buffer >= BUFFER_SIZE - 1024 )
{
msg_Dbg( p_input, "buffer size exeded" );
break;
}
}
p_ans = http_answer_parse( p_sys->buffer, p_sys->i_buffer );
if( !p_ans )
{
msg_Err( p_input, "cannot parse answer" );
return VLC_EGENERIC;
}
if( p_ans->i_error < 200 || p_ans->i_error >= 300 )
{
msg_Err( p_input, "error %d (server return=`%s')", p_ans->i_error, p_ans->psz_answer );
http_answer_free( p_ans );
return VLC_EGENERIC;
}
if( !p_ans->p_body )
{
p_sys->i_buffer_pos = 0;
p_sys->i_buffer = 0;
}
else
{
p_sys->i_buffer_pos = p_ans->p_body - p_sys->buffer;
}
http_answer_free( p_ans );
return VLC_SUCCESS;
}
static void mmsh_stop( input_thread_t *p_input, access_sys_t *p_sys )
{
msg_Dbg( p_input, "closing stream" );
NetClose( p_input, p_sys->p_socket );
}
static ssize_t NetFill( input_thread_t *p_input,
access_sys_t *p_sys, int i_size )
{
int i_try = 0;
int i_total = 0;
i_size = __MIN( i_size, BUFFER_SIZE - p_sys->i_buffer );
if( i_size <= 0 )
{
return 0;
}
for( ;; )
{
int i_read;
i_read =
NetRead( p_input, p_sys->p_socket, &p_sys->buffer[p_sys->i_buffer], i_size );
if( i_read == 0 )
{
if( i_try++ > 2 )
{
break;
}
msg_Dbg( p_input, "another try %d/2", i_try );
continue;
}
if( i_read < 0 || p_input->b_die || p_input->b_error )
{
break;
}
i_total += i_read;
p_sys->i_buffer += i_read;
if( i_total >= i_size )
{
break;
}
}
p_sys->buffer[p_sys->i_buffer] = '\0';
return i_total;
}
/****************************************************************************
* NetOpenTCP:
****************************************************************************/
static input_socket_t * NetOpenTCP( input_thread_t *p_input, url_t *p_url )
{
input_socket_t *p_socket;
char *psz_network;
module_t *p_network;
network_socket_t socket_desc;
p_socket = malloc( sizeof( input_socket_t ) );
memset( p_socket, 0, sizeof( input_socket_t ) );
psz_network = "";
if( config_GetInt( p_input, "ipv4" ) )
{
psz_network = "ipv4";
}
else if( config_GetInt( p_input, "ipv6" ) )
{
psz_network = "ipv6";
}
msg_Dbg( p_input, "waiting for connection..." );
socket_desc.i_type = NETWORK_TCP;
socket_desc.psz_server_addr = p_url->psz_host;
socket_desc.i_server_port = p_url->i_port;
socket_desc.psz_bind_addr = "";
socket_desc.i_bind_port = 0;
p_input->p_private = (void*)&socket_desc;
if( !( p_network = module_Need( p_input, "network", psz_network ) ) )
{
msg_Err( p_input, "failed to connect with server" );
return NULL;
}
module_Unneed( p_input, p_network );
p_socket->i_handle = socket_desc.i_handle;
p_input->i_mtu = socket_desc.i_mtu;
msg_Dbg( p_input,
"connection with \"%s:%d\" successful",
p_url->psz_host,
p_url->i_port );
return p_socket;
}
/*****************************************************************************
* Read: read on a file descriptor, checking b_die periodically
*****************************************************************************/
static ssize_t NetRead( input_thread_t *p_input,
input_socket_t *p_socket,
byte_t *p_buffer, size_t i_len )
{
struct timeval timeout;
fd_set fds;
ssize_t i_recv;
int i_ret;
/* Initialize file descriptor set */
FD_ZERO( &fds );
FD_SET( p_socket->i_handle, &fds );
/* We'll wait 1 second if nothing happens */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
/* Find if some data is available */
while( ( i_ret = select( p_socket->i_handle + 1, &fds,
NULL, NULL, &timeout )) == 0 ||
#ifdef HAVE_ERRNO_H
( i_ret < 0 && errno == EINTR )
#endif
)
{
FD_ZERO( &fds );
FD_SET( p_socket->i_handle, &fds );
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if( p_input->b_die || p_input->b_error )
{
return 0;
}
}
if( i_ret < 0 )
{
msg_Err( p_input, "network select error (%s)", strerror(errno) );
return -1;
}
i_recv = recv( p_socket->i_handle, p_buffer, i_len, 0 );
if( i_recv < 0 )
{
msg_Err( p_input, "recv failed (%s)", strerror(errno) );
}
return i_recv;
}
static ssize_t NetWrite( input_thread_t *p_input,
input_socket_t *p_socket,
byte_t *p_buffer, size_t i_len )
{
struct timeval timeout;
fd_set fds;
ssize_t i_send;
int i_ret;
/* Initialize file descriptor set */
FD_ZERO( &fds );
FD_SET( p_socket->i_handle, &fds );
/* We'll wait 1 second if nothing happens */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
/* Find if some data is available */
while( ( i_ret = select( p_socket->i_handle + 1, NULL, &fds, NULL, &timeout ) ) == 0 ||
#ifdef HAVE_ERRNO_H
( i_ret < 0 && errno == EINTR )
#endif
)
{
FD_ZERO( &fds );
FD_SET( p_socket->i_handle, &fds );
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if( p_input->b_die || p_input->b_error )
{
return 0;
}
}
if( i_ret < 0 )
{
msg_Err( p_input, "network select error (%s)", strerror(errno) );
return -1;
}
i_send = send( p_socket->i_handle, p_buffer, i_len, 0 );
if( i_send < 0 )
{
msg_Err( p_input, "send failed (%s)", strerror(errno) );
}
return i_send;
}
static void NetClose( input_thread_t *p_input, input_socket_t *p_socket )
{
#if defined( WIN32 ) || defined( UNDER_CE )
closesocket( p_socket->i_handle );
#else
close( p_socket->i_handle );
#endif
free( p_socket );
}
static int http_next_line( uint8_t **pp_data, int *pi_data )
{
char *p, *p_end = *pp_data + *pi_data;
for( p = *pp_data; p < p_end; p++ )
{
if( p + 1 < p_end && *p == '\n' )
{
*pi_data = p_end - p - 1;
*pp_data = p + 1;
return VLC_SUCCESS;
}
if( p + 2 < p_end && p[0] == '\r' && p[1] == '\n' )
{
*pi_data = p_end - p - 2;
*pp_data = p + 2;
return VLC_SUCCESS;
}
}
*pi_data = 0;
*pp_data = p_end;
return VLC_EGENERIC;
}
static http_answer_t *http_answer_parse( uint8_t *p_data, int i_data )
{
http_answer_t *ans = malloc( sizeof( http_answer_t ) );
http_field_t **pp_last;
char buffer[1024];
if( sscanf( p_data, "HTTP/1.%d %d %s", &ans-> i_version, &ans->i_error, buffer ) < 3 )
{
free( ans );
return NULL;
}
ans->psz_answer = strdup( buffer );
fprintf( stderr, "version=%d error=%d answer=%s\n", ans-> i_version, ans->i_error, ans->psz_answer );
ans->p_fields = NULL;
ans->i_body = 0;
ans->p_body = 0;
pp_last = &ans->p_fields;
for( ;; )
{
http_field_t *p_field;
uint8_t *colon;
if( http_next_line( &p_data, &i_data ) )
{
return ans;
}
if( !strncmp( p_data, "\r\n", 2 ) || !strncmp( p_data, "\n", 1 ) )
{
break;
}
colon = strstr( p_data, ": " );
if( colon )
{
uint8_t *end;
end = strstr( colon, "\n" ) - 1;
if( *end != '\r' )
{
end++;
}
p_field = malloc( sizeof( http_field_t ) );
p_field->psz_name = strndup( p_data, colon - p_data );
p_field->psz_value = strndup( colon + 2, end - colon - 2 );
p_field->p_next = NULL;
*pp_last = p_field;
pp_last = &p_field->p_next;
fprintf( stderr, "field name=`%s' value=`%s'\n", p_field->psz_name, p_field->psz_value );
}
}
if( http_next_line( &p_data, &i_data ) )
{
return ans;
}
ans->p_body = p_data;
ans->i_body = i_data;
fprintf( stderr, "body size=%d\n", i_data );
return ans;
}
static void http_answer_free( http_answer_t *ans )
{
http_field_t *p_field = ans->p_fields;
while( p_field )
{
http_field_t *p_next;
p_next = p_field->p_next;
free( p_field->psz_name );
free( p_field->psz_value );
free( p_field );
p_field = p_next;
}
free( ans->psz_answer );
free( ans );
}
static http_field_t *http_field_find( http_field_t *p_field, char *psz_name )
{
while( p_field )
{
if( !strcasecmp( p_field->psz_name, psz_name ) )
{
return p_field;
}
p_field = p_field->p_next;
}
return NULL;
}
static char *http_field_get_value( http_answer_t *ans, char *psz_name )
{
http_field_t *p_field = ans->p_fields;
while( p_field )
{
if( !strcasecmp( p_field->psz_name, psz_name ) )
{
return p_field->psz_value;
}
p_field = p_field->p_next;
}
return NULL;
}
static int chunk_parse( chunk_t *ck, uint8_t *p_data, int i_data )
{
if( i_data < 12 )
{
return VLC_EGENERIC;
}
ck->i_type = GetWLE( p_data );
ck->i_size = GetWLE( p_data + 2);
ck->i_sequence = GetDWLE( p_data + 4);
ck->i_unknown = GetWLE( p_data + 8);
ck->i_size2 = GetWLE( p_data + 10);
ck->p_data = p_data + 12;
ck->i_data = __MIN( i_data - 12, ck->i_size2 - 8 );
#if 0
fprintf( stderr, "type=0x%x size=%d sequence=%d unknown=%d size2=%d data=%d\n",
ck->i_type, ck->i_size, ck->i_sequence, ck->i_unknown, ck->i_size2, ck->i_data );
#endif
return VLC_SUCCESS;
}
/*****************************************************************************
* mmsh.h:
*****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN
* $Id: mmsh.h,v 1.1 2003/04/20 19:29:43 fenrir Exp $
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#if 0
/* url: [/]host[:port][/path][@username[:password]] */
typedef struct url_s
{
char *psz_host;
int i_port;
char *psz_path;
char *psz_username;
char *psz_password;
} url_t;
static url_t *url_new ( char * );
static void url_free ( url_t * );
#endif
typedef struct
{
uint16_t i_type;
uint16_t i_size;
uint32_t i_sequence;
uint16_t i_unknown;
uint16_t i_size2;
int i_data;
uint8_t *p_data;
} chunk_t;
static int chunk_parse( chunk_t *, uint8_t *, int );
#define BUFFER_SIZE 150000
struct access_sys_t
{
int i_proto;
input_socket_t *p_socket;
url_t *p_url;
int i_request_context;
int i_buffer;
int i_buffer_pos;
uint8_t buffer[BUFFER_SIZE + 1];
vlc_bool_t b_broadcast;
uint8_t *p_header;
int i_header;
uint8_t *p_packet;
uint32_t i_packet_sequence;
unsigned int i_packet_used;
unsigned int i_packet_length;
off_t i_pos;
asf_header_t asfh;
guid_t guid;
};
static input_socket_t * NetOpenTCP ( input_thread_t *, url_t * );
static ssize_t NetRead ( input_thread_t *, input_socket_t *, byte_t *, size_t );
static ssize_t NetWrite ( input_thread_t *, input_socket_t *, byte_t *, size_t );
static void NetClose ( input_thread_t *, input_socket_t * );
static ssize_t NetFill( input_thread_t *, access_sys_t *, int );
typedef struct http_field_s
{
char *psz_name;
char *psz_value;
struct http_field_s *p_next;
} http_field_t;
typedef struct
{
int i_version;
int i_error;
char *psz_answer;
http_field_t *p_fields;
uint8_t *p_body;
int i_body;
} http_answer_t;
static http_answer_t *http_answer_parse ( uint8_t *, int );
static void http_answer_free ( http_answer_t * );
static char *http_field_get_value ( http_answer_t *, char * );
static http_field_t *http_field_find ( http_field_t *, char * );
static int mmsh_start( input_thread_t *, access_sys_t *, off_t );
static void mmsh_stop ( input_thread_t *, access_sys_t * );
/*****************************************************************************
* mms.c: MMS access plug-in
*****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN
* $Id: mmstu.c,v 1.1 2003/04/20 19:29:43 fenrir Exp $
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <vlc/vlc.h>
#include <vlc/input.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
# ifndef IN_MULTICAST
# define IN_MULTICAST(a) IN_CLASSD(a)
# endif
#else
# include <sys/socket.h>
# include <netinet/in.h>
# if HAVE_ARPA_INET_H
# include <arpa/inet.h>
# elif defined( SYS_BEOS )
# include <net/netdb.h>
# endif
#endif
#include "network.h"
#include "asf.h"
#include "buffer.h"
#include "mms.h"
#include "mmstu.h"
#undef MMS_DEBUG
/****************************************************************************
* NOTES:
* MMSProtocole documentation found at http://get.to/sdp
****************************************************************************/
/*****************************************************************************
* Local prototypes
*****************************************************************************/
int E_( MMSTUOpen ) ( input_thread_t * );
void E_( MMSTUClose ) ( input_thread_t * );
static int Read ( input_thread_t * p_input, byte_t * p_buffer,
size_t i_len );
static void Seek ( input_thread_t *, off_t );
static int MMSOpen( input_thread_t *, url_t *, int, char * );
static int MMSStart ( input_thread_t *, uint32_t );
static int MMSStop ( input_thread_t *p_input );
static int MMSClose ( input_thread_t * );
static int mms_CommandRead( input_thread_t *p_input, int i_command1, int i_command2 );
static int mms_CommandSend( input_thread_t *, int, uint32_t, uint32_t, uint8_t *, int );
static int mms_HeaderMediaRead( input_thread_t *, int );
static int mms_ReceivePacket( input_thread_t * );
//static void mms_ParseURL( url_t *p_url, char *psz_url );
/*
* XXX DON'T FREE MY MEMORY !!! XXX
* non mais :P
*/
/*
* Ok, ok, j'le ferai plus...
*/
/*
* Merci :))
*/
/*
* Vous pourriez signer vos commentaires (mme si on voit bien qui peut
* crire ce genre de trucs :p), et crire en anglais, bordel de
* merde :p.
*/
int E_( MMSTUOpen ) ( input_thread_t *p_input )
{
access_sys_t *p_sys;
int i_proto;
char *psz_network;
int i_status;
/* *** allocate p_sys_data *** */
p_input->p_access_data = p_sys = malloc( sizeof( access_sys_t ) );
memset( p_sys, 0, sizeof( access_sys_t ) );
/* *** Parse URL and get server addr/port and path *** */
//mms_ParseURL( &p_sys->url, p_input->psz_name );
p_sys->p_url = E_( url_new )( p_input->psz_name );
if( *p_sys->p_url->psz_host == '\0' )
{
E_( url_free )( p_sys->p_url );
msg_Err( p_input, "invalid server name" );
return( -1 );
}
if( p_sys->p_url->i_port <= 0 )
{
p_sys->p_url->i_port = 1755;
}
#if 0
if( p_sys->url.i_bind_port == 0 )
{
p_sys->url.i_bind_port = 7000; /* default port */
}
#endif
/* *** connect to this server *** */
/* 1: look at requested protocol (udp/tcp) */
i_proto = MMS_PROTO_AUTO;
if( *p_input->psz_access )
{
if( !strncmp( p_input->psz_access, "mmsu", 4 ) )
{
i_proto = MMS_PROTO_UDP;
}
else if( !strncmp( p_input->psz_access, "mmst", 4 ) )
{
i_proto = MMS_PROTO_TCP;
}
}
/* 2: look at ip version ipv4/ipv6 */
psz_network = "";
if( config_GetInt( p_input, "ipv4" ) )
{
psz_network = "ipv4";
}
else if( config_GetInt( p_input, "ipv6" ) )
{
psz_network = "ipv6";
}
/* 3: connect */
if( i_proto == MMS_PROTO_AUTO )
{ /* first try with TCP */
i_status =
MMSOpen( p_input, p_sys->p_url, MMS_PROTO_TCP, psz_network );
if( i_status < 0 )
{ /* then with UDP */
i_status =
MMSOpen( p_input, p_sys->p_url, MMS_PROTO_UDP, psz_network );
}
}
else
{
i_status =
MMSOpen( p_input, p_sys->p_url, i_proto, psz_network );
}
if( i_status < 0 )
{
msg_Err( p_input, "cannot connect to server" );
E_( url_free )( p_sys->p_url );
return( -1 );
}
msg_Dbg( p_input, "connected to %s:%d", p_sys->p_url->psz_host, p_sys->p_url->i_port );
/* *** set exported functions *** */
p_input->pf_read = Read;
p_input->pf_seek = Seek;
p_input->pf_set_program = input_SetProgram;
p_input->pf_set_area = NULL;
p_input->p_private = NULL;
/* *** finished to set some variable *** */
vlc_mutex_lock( &p_input->stream.stream_lock );
p_input->stream.b_pace_control = 0;
p_input->stream.p_selected_area->i_tell = 0;
/*
* i_flags_broadcast
* yy xx ?? ??
* broadcast yy=0x02, xx= 0x00
* pre-recorded yy=0x01, xx= 0x80 if video, 0x00 no video
*/
if( p_sys->i_packet_count <= 0 || ( p_sys->i_flags_broadcast >> 24 ) == 0x02 )
{
p_input->stream.b_seekable = 0;
p_input->stream.p_selected_area->i_size = 0;
}
else
{
p_input->stream.b_seekable = 1;
p_input->stream.p_selected_area->i_size =
p_sys->i_header +
p_sys->i_packet_count * p_sys->i_packet_length;
}
p_input->stream.i_method = INPUT_METHOD_NETWORK;
vlc_mutex_unlock( &p_input->stream.stream_lock );
/* *** Start stream *** */
if( MMSStart( p_input, 0xffffffff ) < 0 )
{
msg_Err( p_input, "cannot start stream" );
MMSClose( p_input );
E_( url_free )( p_sys->p_url );
return( -1 );
}
/* Update default_pts to a suitable value for mms access */
p_input->i_pts_delay = config_GetInt( p_input, "mms-caching" ) * 1000;
return( 0 );
}
/*****************************************************************************
* Close: free unused data structures
*****************************************************************************/
void E_( MMSTUClose ) ( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
/* close connection with server */
MMSClose( p_input );
/* free memory */
E_( url_free )( p_sys->p_url );
free( p_sys );
}
/*****************************************************************************
* Seek: try to go at the right place
*****************************************************************************/
static void Seek( input_thread_t * p_input, off_t i_pos )
{
access_sys_t *p_sys = p_input->p_access_data;
uint32_t i_packet;
uint32_t i_offset;
var_buffer_t buffer;
if( i_pos < 0 )
{
return;
}
vlc_mutex_lock( &p_input->stream.stream_lock );
if( i_pos < p_sys->i_header)
{
if( p_sys->i_pos < p_sys->i_header )
{
/* no need to restart stream, it was already one
* or no stream was yet read */
p_sys->i_pos = i_pos;
return;
}
else
{
i_packet = 0xffffffff;
i_offset = 0;
}
}
else
{
i_packet = ( i_pos - p_sys->i_header ) / p_sys->i_packet_length;
i_offset = ( i_pos - p_sys->i_header ) % p_sys->i_packet_length;
}
msg_Dbg( p_input, "seeking to "I64Fd " (packet:%d)", i_pos, i_packet );
MMSStop( p_input );
msg_Dbg( p_input, "stream stopped (seek)" );
/* *** restart stream *** */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add64( &buffer, 0 ); /* seek point in second */
var_buffer_add32( &buffer, 0xffffffff );
var_buffer_add32( &buffer, i_packet ); // begin from start
var_buffer_add8( &buffer, 0xff ); // stream time limit
var_buffer_add8( &buffer, 0xff ); // on 3bytes ...
var_buffer_add8( &buffer, 0xff ); //
var_buffer_add8( &buffer, 0x00 ); // don't use limit
var_buffer_add32( &buffer, p_sys->i_media_packet_id_type );
mms_CommandSend( p_input, 0x07, p_sys->i_command_level, 0x0001ffff,
buffer.p_data, buffer.i_data );
var_buffer_free( &buffer );
for( ;; )
{
mms_HeaderMediaRead( p_input, MMS_PACKET_CMD );
if( p_sys->i_command == 0x1e )
{
msg_Dbg( p_input, "received 0x1e (seek)" );
break;
}
}
for( ;; )
{
mms_HeaderMediaRead( p_input, MMS_PACKET_CMD );
if( p_sys->i_command == 0x05 )
{
msg_Dbg( p_input, "received 0x05 (seek)" );
break;
}
}
/* get a packet */
mms_HeaderMediaRead( p_input, MMS_PACKET_MEDIA );
msg_Dbg( p_input, "Streaming restarted" );
p_sys->i_media_used += i_offset;
p_sys->i_pos = i_pos;
p_input->stream.p_selected_area->i_tell = i_pos;
vlc_mutex_unlock( &p_input->stream.stream_lock );
}
static int Read ( input_thread_t * p_input, byte_t * p_buffer,
size_t i_len )
{
access_sys_t *p_sys = p_input->p_access_data;
size_t i_data;
size_t i_copy;
i_data = 0;
/* *** send header if needed ** */
if( p_sys->i_pos < p_sys->i_header )
{
i_copy = __MIN( i_len, p_sys->i_header - p_sys->i_pos );
if( i_copy > 0 )
{
memcpy( p_buffer,
p_sys->p_header + p_sys->i_pos,
i_copy );
}
i_data += i_copy;
}
/* *** now send data if needed *** */
while( i_data < i_len )
{
if( p_sys->i_media_used < p_sys->i_media )
{
i_copy = __MIN( i_len - i_data ,
p_sys->i_media - p_sys->i_media_used );
memcpy( p_buffer + i_data,
p_sys->p_media + p_sys->i_media_used,
i_copy );
i_data += i_copy;
p_sys->i_media_used += i_copy;
}
else if( p_sys->p_media != NULL &&
p_sys->i_media_used < p_sys->i_packet_length )
{
i_copy = __MIN( i_len - i_data,
p_sys->i_packet_length - p_sys->i_media_used);
memset( p_buffer + i_data, 0, i_copy );
i_data += i_copy;
p_sys->i_media_used += i_copy;
}
else
{
if( p_sys->i_eos
|| mms_HeaderMediaRead( p_input, MMS_PACKET_MEDIA ) < 0 )
{
p_sys->i_pos += i_data;
return( i_data );
}
}
}
p_sys->i_pos += i_data;
return( i_data );
}
/****************************************************************************
* MMSOpen : Open a connection with the server over mmst or mmsu
****************************************************************************/
static int MMSOpen( input_thread_t *p_input,
url_t *p_url,
int i_proto,
char *psz_network ) /* "", "ipv4", "ipv6" */
{
module_t *p_network;
access_sys_t *p_sys = p_input->p_access_data;
network_socket_t socket_desc;
int b_udp = ( i_proto == MMS_PROTO_UDP ) ? 1 : 0;
var_buffer_t buffer;
char tmp[4096];
uint16_t *p;
int i_server_version;
int i_tool_version;
int i_update_player_url;
int i_encryption_type;
int i;
int i_streams;
int i_first;
/* *** Open a TCP connection with server *** */
msg_Dbg( p_input, "waiting for connection..." );
socket_desc.i_type = NETWORK_TCP;
socket_desc.psz_server_addr = p_url->psz_host;
socket_desc.i_server_port = p_url->i_port;
socket_desc.psz_bind_addr = "";
socket_desc.i_bind_port = 0;
p_input->p_private = (void*)&socket_desc;
if( !( p_network = module_Need( p_input, "network", psz_network ) ) )
{
msg_Err( p_input, "failed to open a connection (tcp)" );
return( -1 );
}
module_Unneed( p_input, p_network );
p_sys->socket_tcp.i_handle = socket_desc.i_handle;
p_input->i_mtu = 0; /*socket_desc.i_mtu;*/
msg_Dbg( p_input,
"connection(tcp) with \"%s:%d\" successful",
p_url->psz_host,
p_url->i_port );
/* *** Bind port if UDP protocol is selected *** */
if( b_udp )
{
struct sockaddr_in name;
socklen_t i_namelen = sizeof( struct sockaddr_in );
if( getsockname( p_sys->socket_tcp.i_handle,
(struct sockaddr*)&name, &i_namelen ) < 0 )
{
msg_Err( p_input, "for udp you have to provide bind address (mms://<server_addr>@<bind_addr/<path> (FIXME)" );
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_sys->socket_tcp.i_handle );
#elif defined( WIN32 )
closesocket( p_sys->socket_tcp.i_handle );
#else
close( p_sys->socket_tcp.i_handle );
#endif
return( -1 );
}
p_sys->psz_bind_addr = inet_ntoa( name.sin_addr );
socket_desc.i_type = NETWORK_UDP;
socket_desc.psz_server_addr = "";
socket_desc.i_server_port = 0;
socket_desc.psz_bind_addr = p_sys->psz_bind_addr;
socket_desc.i_bind_port = 7000; //p_url->i_bind_port; FIXME
p_input->p_private = (void*)&socket_desc;
if( !( p_network = module_Need( p_input, "network", psz_network ) ) )
{
msg_Err( p_input, "failed to open a connection (udp)" );
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_sys->socket_tcp.i_handle );
#elif defined( WIN32 )
closesocket( p_sys->socket_tcp.i_handle );
#else
close( p_sys->socket_tcp.i_handle );
#endif
return( -1 );
}
module_Unneed( p_input, p_network );
p_sys->socket_udp.i_handle = socket_desc.i_handle;
p_input->i_mtu = 0;/*socket_desc.i_mtu; FIXME */
msg_Dbg( p_input,
"connection(udp) at \"%s:%d\" successful",
p_sys->psz_bind_addr,
7000 );
}
else
{
p_sys->psz_bind_addr = NULL;
}
/* *** Init context for mms prototcol *** */
E_( GenerateGuid )( &p_sys->guid ); /* used to identify client by server */
msg_Dbg( p_input,
"generated guid: "GUID_FMT,
GUID_PRINT( p_sys->guid ) );
p_sys->i_command_level = 1; /* updated after 0x1A command */
p_sys->i_seq_num = 0;
p_sys->i_media_packet_id_type = 0x04;
p_sys->i_header_packet_id_type = 0x02;
p_sys->i_proto = i_proto;
p_sys->i_packet_seq_num = 0;
p_sys->p_header = NULL;
p_sys->i_header = 0;
p_sys->p_media = NULL;
p_sys->i_media = 0;
p_sys->i_media_used = 0;
p_sys->i_pos = 0;
p_sys->i_buffer_tcp = 0;
p_sys->i_buffer_udp = 0;
p_sys->p_cmd = NULL;
p_sys->i_cmd = 0;
p_sys->i_eos = 0;
/* *** send command 1 : connection request *** */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add16( &buffer, 0x001c );
var_buffer_add16( &buffer, 0x0003 );
sprintf( tmp,
"NSPlayer/7.0.0.1956; {"GUID_FMT"}; Host: %s",
GUID_PRINT( p_sys->guid ),
p_url->psz_host );
var_buffer_addUTF16( &buffer, tmp );
mms_CommandSend( p_input,
0x01, /* connexion request */
0x00000000, /* flags, FIXME */
0x0004000b, /* ???? */
buffer.p_data,
buffer.i_data );
if( mms_CommandRead( p_input, 0x01, 0 ) < 0 )
{
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
i_server_version = GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 32 );
i_tool_version = GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 36 );
i_update_player_url = GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 40 );
i_encryption_type = GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 44 );
p = (uint16_t*)( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 48 );
#define GETUTF16( psz, size ) \
{ \
int i; \
psz = malloc( size + 1); \
for( i = 0; i < size; i++ ) \
{ \
psz[i] = p[i]; \
} \
psz[size] = '\0'; \
p += 2 * ( size ); \
}
GETUTF16( p_sys->psz_server_version, i_server_version );
GETUTF16( p_sys->psz_tool_version, i_tool_version );
GETUTF16( p_sys->psz_update_player_url, i_update_player_url );
GETUTF16( p_sys->psz_encryption_type, i_encryption_type );
#undef GETUTF16
msg_Dbg( p_input,
"0x01 --> server_version:\"%s\" tool_version:\"%s\" update_player_url:\"%s\" encryption_type:\"%s\"",
p_sys->psz_server_version,
p_sys->psz_tool_version,
p_sys->psz_update_player_url,
p_sys->psz_encryption_type );
/* *** should make an 18 command to make data timing *** */
/* *** send command 2 : transport protocol selection *** */
var_buffer_reinitwrite( &buffer, 0 );
var_buffer_add32( &buffer, 0x00000000 );
var_buffer_add32( &buffer, 0x000a0000 );
var_buffer_add32( &buffer, 0x00000002 );
if( b_udp )
{
sprintf( tmp,
"\\\\%s\\UDP\\%d",
p_sys->psz_bind_addr,
7000 ); // FIXME
}
else
{
sprintf( tmp, "\\\\192.168.0.1\\TCP\\1242" );
}
var_buffer_addUTF16( &buffer, tmp );
var_buffer_add16( &buffer, '0' );
mms_CommandSend( p_input,
0x02, /* connexion request */
0x00000000, /* flags, FIXME */
0xffffffff, /* ???? */
buffer.p_data,
buffer.i_data );
/* *** response from server, should be 0x02 or 0x03 *** */
mms_CommandRead( p_input, 0x02, 0x03 );
if( p_sys->i_command == 0x03 )
{
msg_Err( p_input,
"%s protocol selection failed", b_udp ? "UDP" : "TCP" );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
else if( p_sys->i_command != 0x02 )
{
msg_Warn( p_input, "received command isn't 0x02 in reponse to 0x02" );
}
/* *** send command 5 : media file name/path requested *** */
var_buffer_reinitwrite( &buffer, 0 );
var_buffer_add64( &buffer, 0 );
var_buffer_addUTF16( &buffer, p_url->psz_path );
mms_CommandSend( p_input,
0x05,
p_sys->i_command_level,
0xffffffff,
buffer.p_data,
buffer.i_data );
/* *** wait for reponse *** */
mms_CommandRead( p_input, 0x1a, 0x06 );
/* test if server send 0x1A answer */
if( p_sys->i_command == 0x1A )
{
msg_Err( p_input, "id/password requested (not yet supported)" );
/* FIXME */
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
if( p_sys->i_command != 0x06 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x06)",
p_sys->i_command );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
/* 1 for file ok, 2 for authen ok */
switch( GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE ) )
{
case 0x0001:
msg_Dbg( p_input, "Media file name/path accepted" );
break;
case 0x0002:
msg_Dbg( p_input, "Authentication accepted" );
break;
case -1:
default:
msg_Err( p_input, "error while asking for file %d",
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE ) );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
p_sys->i_flags_broadcast =
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 12 );
p_sys->i_media_length =
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 24 );
p_sys->i_packet_length =
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 44 );
p_sys->i_packet_count =
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 48 );
p_sys->i_max_bit_rate =
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 56 );
p_sys->i_header_size =
GetDWLE( p_sys->p_cmd + MMS_CMD_HEADERSIZE + 60 );
msg_Dbg( p_input,
"answer 0x06 flags:0x%8.8x media_length:%ds packet_length:%d packet_count:%d max_bit_rate:%d header_size:%d",
p_sys->i_flags_broadcast,
p_sys->i_media_length,
p_sys->i_packet_length,
p_sys->i_packet_count,
p_sys->i_max_bit_rate,
p_sys->i_header_size );
/* XXX XXX dirty hack XXX XXX */
p_input->i_mtu = 3 * p_sys->i_packet_length;
/* *** send command 15 *** */
var_buffer_reinitwrite( &buffer, 0 );
var_buffer_add32( &buffer, 0 );
var_buffer_add32( &buffer, 0x8000 );
var_buffer_add32( &buffer, 0xffffffff );
var_buffer_add32( &buffer, 0x00 );
var_buffer_add32( &buffer, 0x00 );
var_buffer_add32( &buffer, 0x00 );
var_buffer_add64( &buffer, 0x40ac200000000000 );
var_buffer_add32( &buffer, p_sys->i_header_packet_id_type );
mms_CommandSend( p_input, 0x15, p_sys->i_command_level, 0x00,
buffer.p_data, buffer.i_data );
/* *** wait for reponse *** */
mms_CommandRead( p_input, 0x11, 0 );
if( p_sys->i_command != 0x11 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x11)",
p_sys->i_command );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
/* *** now read header packet *** */
/* XXX could be split over multiples packets */
for( ;; )
{
if( mms_HeaderMediaRead( p_input, MMS_PACKET_HEADER ) < 0 )
{
msg_Err( p_input, "cannot receive header" );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
if( p_sys->i_header >= p_sys->i_header_size )
{
msg_Dbg( p_input,
"header complete(%d)",
p_sys->i_header );
break;
}
msg_Dbg( p_input,
"header incomplete (%d/%d), reading more",
p_sys->i_header,
p_sys->i_header_size );
}
/* *** parse header and get stream and their id *** */
/* get all streams properties,
*
* TODO : stream bitrates properties(optional)
* and bitrate mutual exclusion(optional) */
E_( asf_HeaderParse )( &p_sys->asfh,
p_sys->p_header, p_sys->i_header );
E_( asf_StreamSelect)( &p_sys->asfh,
config_GetInt( p_input, "mms-maxbitrate" ),
config_GetInt( p_input, "mms-all" ),
config_GetInt( p_input, "audio" ),
config_GetInt( p_input, "video" ) );
/* *** now select stream we want to receive *** */
/* TODO take care of stream bitrate TODO */
i_streams = 0;
i_first = -1;
var_buffer_reinitwrite( &buffer, 0 );
/* for now, select first audio and video stream */
for( i = 1; i < 128; i++ )
{
if( p_sys->asfh.stream[i].i_cat != ASF_STREAM_UNKNOWN )
{
i_streams++;
if( i_first != -1 )
{
var_buffer_add16( &buffer, 0xffff );
var_buffer_add16( &buffer, i );
}
else
{
i_first = i;
}
if( p_sys->asfh.stream[i].i_selected )
{
var_buffer_add16( &buffer, 0x0000 );
msg_Info( p_input,
"selecting stream[0x%x] %s (%d kb/s)",
i,
( p_sys->asfh.stream[i].i_cat == ASF_STREAM_AUDIO ) ?
"audio" : "video" ,
p_sys->asfh.stream[i].i_bitrate / 1024);
}
else
{
var_buffer_add16( &buffer, 0x0002 );
msg_Info( p_input,
"ignoring stream[0x%x] %s (%d kb/s)",
i,
( p_sys->asfh.stream[i].i_cat == ASF_STREAM_AUDIO ) ?
"audio" : "video" ,
p_sys->asfh.stream[i].i_bitrate / 1024);
}
}
}
if( i_streams == 0 )
{
msg_Err( p_input, "cannot find any stream" );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
mms_CommandSend( p_input, 0x33,
i_streams,
0xffff | ( i_first << 16 ),
buffer.p_data, buffer.i_data );
mms_CommandRead( p_input, 0x21, 0 );
if( p_sys->i_command != 0x21 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x21)",
p_sys->i_command );
var_buffer_free( &buffer );
MMSClose( p_input );
return( -1 );
}
var_buffer_free( &buffer );
msg_Info( p_input, "connection sucessful" );
return( 0 );
}
/****************************************************************************
* MMSStart : Start streaming
****************************************************************************/
static int MMSStart ( input_thread_t *p_input, uint32_t i_packet )
{
access_sys_t *p_sys = p_input->p_access_data;
var_buffer_t buffer;
/* *** start stream from packet 0 *** */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add64( &buffer, 0 ); /* seek point in second */
var_buffer_add32( &buffer, 0xffffffff );
var_buffer_add32( &buffer, i_packet ); // begin from start
var_buffer_add8( &buffer, 0xff ); // stream time limit
var_buffer_add8( &buffer, 0xff ); // on 3bytes ...
var_buffer_add8( &buffer, 0xff ); //
var_buffer_add8( &buffer, 0x00 ); // don't use limit
var_buffer_add32( &buffer, p_sys->i_media_packet_id_type );
mms_CommandSend( p_input, 0x07, p_sys->i_command_level, 0x0001ffff,
buffer.p_data, buffer.i_data );
var_buffer_free( &buffer );
mms_CommandRead( p_input, 0x05, 0 );
if( p_sys->i_command != 0x05 )
{
msg_Err( p_input,
"unknown answer (0x%x instead of 0x05)",
p_sys->i_command );
return( -1 );
}
else
{
/* get a packet */
mms_HeaderMediaRead( p_input, MMS_PACKET_MEDIA );
msg_Dbg( p_input, "Streaming started" );
return( 0 );
}
}
/****************************************************************************
* MMSStop : Stop streaming
****************************************************************************/
static int MMSStop ( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
/* *** stop stream but keep connection alive *** */
mms_CommandSend( p_input,
0x09,
p_sys->i_command_level,
0x001fffff,
NULL, 0 );
return( 0 );
}
/****************************************************************************
* MMSClose : Close streaming and connection
****************************************************************************/
static int MMSClose ( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
msg_Dbg( p_input, "Connection closed" );
/* *** tell server that we will disconnect *** */
mms_CommandSend( p_input,
0x0d,
p_sys->i_command_level,
0x00000001,
NULL, 0 );
/* *** close sockets *** */
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_sys->socket_tcp.i_handle );
#elif defined( WIN32 )
closesocket( p_sys->socket_tcp.i_handle );
#else
close( p_sys->socket_tcp.i_handle );
#endif
if( p_sys->i_proto == MMS_PROTO_UDP )
{
#if defined( UNDER_CE )
CloseHandle( (HANDLE)p_sys->socket_udp.i_handle );
#elif defined( WIN32 )
closesocket( p_sys->socket_udp.i_handle );
#else
close( p_sys->socket_udp.i_handle );
#endif
}
FREE( p_sys->p_cmd );
FREE( p_sys->p_media );
FREE( p_sys->p_header );
FREE( p_sys->psz_server_version );
FREE( p_sys->psz_tool_version );
FREE( p_sys->psz_update_player_url );
FREE( p_sys->psz_encryption_type );
return( 0 );
}
/****************************************************************************
*
* MMS specific functions
*
****************************************************************************/
static int mms_CommandSend( input_thread_t *p_input,
int i_command,
uint32_t i_prefix1, uint32_t i_prefix2,
uint8_t *p_data, int i_data )
{
var_buffer_t buffer;
access_sys_t *p_sys = p_input->p_access_data;
int i_data_by8;
i_data_by8 = ( i_data + 7 ) / 8;
/* first init uffer */
var_buffer_initwrite( &buffer, 0 );
var_buffer_add32( &buffer, 0x00000001 ); /* start sequence */
var_buffer_add32( &buffer, 0xB00BFACE );
/* size after protocol type */
var_buffer_add32( &buffer, i_data + MMS_CMD_HEADERSIZE - 16 );
var_buffer_add32( &buffer, 0x20534d4d ); /* protocol "MMS " */
var_buffer_add32( &buffer, i_data_by8 + 4 );
var_buffer_add32( &buffer, p_sys->i_seq_num ); p_sys->i_seq_num++;
var_buffer_add64( &buffer, 0 );
var_buffer_add32( &buffer, i_data_by8 + 2 );
var_buffer_add32( &buffer, 0x00030000 | i_command ); /* dir | command */
var_buffer_add32( &buffer, i_prefix1 ); /* command specific */
var_buffer_add32( &buffer, i_prefix2 ); /* command specific */
/* specific command data */
if( p_data && i_data > 0 )
{
var_buffer_addmemory( &buffer, p_data, i_data );
}
/* send it */
if( send( p_sys->socket_tcp.i_handle,
buffer.p_data,
buffer.i_data,
0 ) == -1 )
{
msg_Err( p_input, "failed to send command" );
return( -1 );
}
var_buffer_free( &buffer );
return( 0 );
}
static int NetFillBuffer( input_thread_t *p_input )
{
#ifdef UNDER_CE
return -1;
#else
access_sys_t *p_sys = p_input->p_access_data;
struct timeval timeout;
fd_set fds;
int i_ret;
/* FIXME when using udp */
ssize_t i_tcp, i_udp;
ssize_t i_tcp_read, i_udp_read;
int i_handle_max;
/* Initialize file descriptor set */
FD_ZERO( &fds );
i_tcp = MMS_BUFFER_SIZE/2 - p_sys->i_buffer_tcp;
if( p_sys->i_proto == MMS_PROTO_UDP )
{
i_udp = MMS_BUFFER_SIZE/2 - p_sys->i_buffer_udp;
}
else
{
i_udp = 0; /* there isn't udp socket */
}
i_handle_max = 0;
if( i_tcp > 0 )
{
FD_SET( p_sys->socket_tcp.i_handle, &fds );
i_handle_max = __MAX( i_handle_max, p_sys->socket_tcp.i_handle );
}
if( i_udp > 0 )
{
FD_SET( p_sys->socket_udp.i_handle, &fds );
i_handle_max = __MAX( i_handle_max, p_sys->socket_udp.i_handle );
}
if( i_handle_max == 0 )
{
msg_Warn( p_input, "nothing to read %d:%d", i_tcp, i_udp );
return( 0 );
}
else
{
// msg_Warn( p_input, "ask for tcp:%d udp:%d", i_tcp, i_udp );
}
/* We'll wait 0.5 second if nothing happens */
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
/* Find if some data is available */
while( (i_ret = select( i_handle_max + 1, &fds,
NULL, NULL, &timeout )) == 0
|| (i_ret < 0 && errno == EINTR) )
{
FD_ZERO( &fds );
if( i_tcp > 0 ) FD_SET( p_sys->socket_tcp.i_handle, &fds );
if( i_udp > 0 ) FD_SET( p_sys->socket_udp.i_handle, &fds );
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
if( p_input->b_die || p_input->b_error )
{
return 0;
}
}
if( i_ret < 0 )
{
msg_Err( p_input, "network select error (%s)", strerror(errno) );
return -1;
}
if( i_tcp > 0 && FD_ISSET( p_sys->socket_tcp.i_handle, &fds ) )
{
i_tcp_read =
recv( p_sys->socket_tcp.i_handle,
p_sys->buffer_tcp + p_sys->i_buffer_tcp,
i_tcp + MMS_BUFFER_SIZE/2, 0 );
}
else
{
i_tcp_read = 0;
}
if( i_udp > 0 && FD_ISSET( p_sys->socket_udp.i_handle, &fds ) )
{
i_udp_read = recv( p_sys->socket_udp.i_handle,
p_sys->buffer_udp + p_sys->i_buffer_udp,
i_udp + MMS_BUFFER_SIZE/2, 0 );
}
else
{
i_udp_read = 0;
}
#if MMS_DEBUG
if( p_sys->i_proto == MMS_PROTO_UDP )
{
msg_Dbg( p_input,
"filling buffer TCP:%d+%d UDP:%d+%d",
p_sys->i_buffer_tcp,
i_tcp_read,
p_sys->i_buffer_udp,
i_udp_read );
}
else
{
msg_Dbg( p_input,
"filling buffer TCP:%d+%d",
p_sys->i_buffer_tcp,
i_tcp_read );
}
#endif
if( i_tcp_read > 0 )
{
p_sys->i_buffer_tcp += i_tcp_read;
}
if( i_udp_read > 0 )
{
p_sys->i_buffer_udp += i_udp_read;
}
return( i_tcp_read + i_udp_read);
#endif
}
static int mms_ParseCommand( input_thread_t *p_input,
uint8_t *p_data,
int i_data,
int *pi_used )
{
#define GET32( i_pos ) \
( p_sys->p_cmd[i_pos] + ( p_sys->p_cmd[i_pos +1] << 8 ) + \
( p_sys->p_cmd[i_pos + 2] << 16 ) + \
( p_sys->p_cmd[i_pos + 3] << 24 ) )
access_sys_t *p_sys = p_input->p_access_data;
int i_length;
uint32_t i_id;
if( p_sys->p_cmd )
{
free( p_sys->p_cmd );
}
p_sys->i_cmd = i_data;
p_sys->p_cmd = malloc( i_data );
memcpy( p_sys->p_cmd, p_data, i_data );
*pi_used = i_data; /* by default */
if( i_data < MMS_CMD_HEADERSIZE )
{
msg_Warn( p_input, "truncated command (header incomplete)" );
p_sys->i_command = 0;
return( -1 );
}
i_id = GetDWLE( p_data + 4 );
i_length = GetDWLE( p_data + 8 ) + 16;
if( i_id != 0xb00bface )
{
msg_Err( p_input,
"incorrect command header (0x%x)", i_id );
p_sys->i_command = 0;
return( -1 );
}
if( i_length > p_sys->i_cmd )
{
msg_Warn( p_input,
"truncated command (missing %d bytes)",
i_length - i_data );
p_sys->i_command = 0;
return( -1 );
}
else if( i_length < p_sys->i_cmd )
{
p_sys->i_cmd = i_length;
*pi_used = i_length;
}
msg_Dbg( p_input,
"recv command start_sequence:0x%8.8x command_id:0x%8.8x length:%d len8:%d sequence 0x%8.8x len8_II:%d dir_comm:0x%8.8x",
GET32( 0 ),
GET32( 4 ),
GET32( 8 ),
/* 12: protocol type "MMS " */
GET32( 16 ),
GET32( 20 ),
/* 24: unknown (0) */
/* 28: unknown (0) */
GET32( 32 ),
GET32( 36 )
/* 40: switches */
/* 44: extra */ );
p_sys->i_command = GET32( 36 ) & 0xffff;
return( MMS_PACKET_CMD );
}
static int mms_ParsePacket( input_thread_t *p_input,
uint8_t *p_data, size_t i_data,
int *pi_used )
{
access_sys_t *p_sys = p_input->p_access_data;
int i_packet_seq_num;
size_t i_packet_length;
uint32_t i_packet_id;
uint8_t *p_packet;
*pi_used = i_data; /* default */
if( i_data <= 8 )
{
msg_Warn( p_input, "truncated packet (header incomplete)" );
return( -1 );
}
i_packet_id = p_data[4];
i_packet_seq_num = GetDWLE( p_data );
i_packet_length = GetWLE( p_data + 6 );
//msg_Warn( p_input, "------->i_packet_length=%d, i_data=%d", i_packet_length, i_data );
if( i_packet_length > i_data || i_packet_length <= 8)
{
msg_Dbg( p_input,
"truncated packet (missing %d bytes)",
i_packet_length - i_data );
*pi_used = 0;
return( -1 );
}
else if( i_packet_length < i_data )
{
*pi_used = i_packet_length;
}
if( i_packet_id == 0xff )
{
msg_Warn( p_input,
"receive MMS UDP pair timing" );
return( MMS_PACKET_UDP_TIMING );
}
if( i_packet_id != p_sys->i_header_packet_id_type &&
i_packet_id != p_sys->i_media_packet_id_type )
{
msg_Warn( p_input, "incorrect Packet Id Type (0x%x)", i_packet_id );
return( -1 );
}
/* we now have a media or a header packet */
p_packet = malloc( i_packet_length - 8 ); // don't bother with preheader
memcpy( p_packet, p_data + 8, i_packet_length - 8 );
if( i_packet_seq_num != p_sys->i_packet_seq_num )
{
/* FIXME for udp could be just wrong order ? */
msg_Warn( p_input,
"detected packet lost (%d != %d)",
i_packet_seq_num,
p_sys->i_packet_seq_num );
p_sys->i_packet_seq_num = i_packet_seq_num;
}
p_sys->i_packet_seq_num++;
if( i_packet_id == p_sys->i_header_packet_id_type )
{
if( p_sys->p_header )
{
p_sys->p_header = realloc( p_sys->p_header,
p_sys->i_header + i_packet_length - 8 );
memcpy( &p_sys->p_header[p_sys->i_header],
p_packet,
i_packet_length - 8 );
p_sys->i_header += i_packet_length - 8;
free( p_packet );
}
else
{
p_sys->p_header = p_packet;
p_sys->i_header = i_packet_length - 8;
}
/* msg_Dbg( p_input,
"receive header packet (%d bytes)",
i_packet_length - 8 ); */
return( MMS_PACKET_HEADER );
}
else
{
FREE( p_sys->p_media );
p_sys->p_media = p_packet;
p_sys->i_media = i_packet_length - 8;
p_sys->i_media_used = 0;
/* msg_Dbg( p_input,
"receive media packet (%d bytes)",
i_packet_length - 8 ); */
return( MMS_PACKET_MEDIA );
}
}
static int mms_ReceivePacket( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
int i_packet_tcp_type;
int i_packet_udp_type;
for( ;; )
{
if( NetFillBuffer( p_input ) < 0 )
{
msg_Warn( p_input, "cannot fill buffer" );
continue;
}
i_packet_tcp_type = -1;
i_packet_udp_type = -1;
if( p_sys->i_buffer_tcp > 0 )
{
int i_used;
if( GetDWLE( p_sys->buffer_tcp + 4 ) == 0xb00bface )
{
i_packet_tcp_type =
mms_ParseCommand( p_input,
p_sys->buffer_tcp,
p_sys->i_buffer_tcp,
&i_used );
}
else
{
i_packet_tcp_type =
mms_ParsePacket( p_input,
p_sys->buffer_tcp,
p_sys->i_buffer_tcp,
&i_used );
}
if( i_used > 0 && i_used < MMS_BUFFER_SIZE )
{
memmove( p_sys->buffer_tcp,
p_sys->buffer_tcp + i_used,
MMS_BUFFER_SIZE - i_used );
}
p_sys->i_buffer_tcp -= i_used;
}
else if( p_sys->i_buffer_udp > 0 )
{
int i_used;
i_packet_udp_type =
mms_ParsePacket( p_input,
p_sys->buffer_udp,
p_sys->i_buffer_udp,
&i_used );
if( i_used > 0 && i_used < MMS_BUFFER_SIZE )
{
memmove( p_sys->buffer_udp,
p_sys->buffer_udp + i_used,
MMS_BUFFER_SIZE - i_used );
}
p_sys->i_buffer_udp -= i_used;
}
if( i_packet_tcp_type == MMS_PACKET_CMD &&
p_sys->i_command == 0x1b )
{
mms_CommandSend( p_input, 0x1b, 0, 0, NULL, 0 );
i_packet_tcp_type = -1;
}
if( i_packet_tcp_type != -1 )
{
return( i_packet_tcp_type );
}
else if( i_packet_udp_type != -1 )
{
return( i_packet_udp_type );
}
}
}
static int mms_ReceiveCommand( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
for( ;; )
{
int i_used;
int i_status;
if( NetFillBuffer( p_input ) < 0 )
{
msg_Warn( p_input, "cannot fill buffer" );
continue;
}
if( p_sys->i_buffer_tcp > 0 )
{
i_status = mms_ParseCommand( p_input,
p_sys->buffer_tcp,
p_sys->i_buffer_tcp,
&i_used );
if( i_used < MMS_BUFFER_SIZE )
{
memmove( p_sys->buffer_tcp,
p_sys->buffer_tcp + i_used,
MMS_BUFFER_SIZE - i_used );
}
p_sys->i_buffer_tcp -= i_used;
if( i_status < 0 )
{
return( -1 );
}
if( p_sys->i_command == 0x1b )
{
mms_CommandSend( p_input, 0x1b, 0, 0, NULL, 0 );
}
else
{
break;
}
}
else
{
return( -1 );
}
}
return( 0 );
}
#define MMS_RETRY_MAX 10
#define MMS_RETRY_SLEEP 50000
static int mms_CommandRead( input_thread_t *p_input, int i_command1, int i_command2 )
{
access_sys_t *p_sys = p_input->p_access_data;
int i_count;
int i_status;
for( i_count = 0; i_count < MMS_RETRY_MAX; )
{
i_status = mms_ReceiveCommand( p_input );
if( i_status < 0 || p_sys->i_command == 0 )
{
i_count++;
msleep( MMS_RETRY_SLEEP );
}
else if( i_command1 == 0 && i_command2 == 0)
{
return( 0 );
}
else if( p_sys->i_command == i_command1 || p_sys->i_command == i_command2 )
{
return( 0 );
}
else
{
switch( p_sys->i_command )
{
case 0x03:
msg_Warn( p_input, "socket closed by server" );
p_sys->i_eos = 1;
return( -1 );
case 0x1e:
msg_Warn( p_input, "end of media stream" );
p_sys->i_eos = 1;
return( -1 );
default:
break;
}
}
}
msg_Warn( p_input, "failed to receive command (abording)" );
return( -1 );
}
static int mms_HeaderMediaRead( input_thread_t *p_input, int i_type )
{
access_sys_t *p_sys = p_input->p_access_data;
int i_count;
for( i_count = 0; i_count < MMS_RETRY_MAX; )
{
int i_status;
i_status = mms_ReceivePacket( p_input );
if( i_status < 0 )
{
i_count++;
msg_Warn( p_input,
"cannot receive header (%d/%d)", i_count, MMS_RETRY_MAX );
msleep( MMS_RETRY_SLEEP );
}
else if( i_status == i_type || i_type == MMS_PACKET_ANY )
{
return( i_type );
}
else if( i_status == MMS_PACKET_CMD )
{
switch( p_sys->i_command )
{
case 0x03:
msg_Warn( p_input, "socket closed by server" );
p_sys->i_eos = 1;
return( -1 );
case 0x1e:
msg_Warn( p_input, "end of media stream" );
p_sys->i_eos = 1;
return( -1 );
case 0x20:
/* XXX not too dificult to be done EXCEPT that we
* need to restart demuxer... and I don't see how we
* could do that :p */
msg_Err( p_input,
"reinitialization needed --> unsupported" );
p_sys->i_eos = 1;
return( -1 );
default:
break;
}
}
}
msg_Err( p_input,
"cannot receive %s (abording)",
( i_type == MMS_PACKET_HEADER ) ? "header" : "media data" );
return( -1 );
}
/*****************************************************************************
* mms.h: MMS access plug-in
*****************************************************************************
* Copyright (C) 2001, 2002 VideoLAN
* $Id: mmstu.h,v 1.1 2003/04/20 19:29:43 fenrir Exp $
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#if 0
/* url: [/]host[:port][/path] */
typedef struct url_s
{
char *psz_server_addr;
int i_server_port;
char *psz_bind_addr;
int i_bind_port;
char *psz_path;
/* private */
char *psz_private;
} url_t;
#endif
#define MMS_PACKET_ANY 0
#define MMS_PACKET_CMD 1
#define MMS_PACKET_HEADER 2
#define MMS_PACKET_MEDIA 3
#define MMS_PACKET_UDP_TIMING 4
#define MMS_CMD_HEADERSIZE 48
#if 0
#define MMS_STREAM_VIDEO 0x0001
#define MMS_STREAM_AUDIO 0x0002
#define MMS_STREAM_UNKNOWN 0xffff
typedef struct mms_stream_s
{
int i_id; /* 1 -> 127 */
int i_cat; /* MMS_STREAM_VIDEO, MMS_STREAM_AUDIO */
int i_bitrate; /* -1 if unknown */
int i_selected;
} mms_stream_t;
#endif
#define MMS_BUFFER_SIZE 100000
struct access_sys_t
{
int i_proto; /* MMS_PROTO_TCP, MMS_PROTO_UDP */
input_socket_t socket_tcp; /* TCP socket for communication with server */
input_socket_t socket_udp; /* Optional UDP socket for data(media/header packet) */
/* send by server */
char *psz_bind_addr; /* used by udp */
url_t *p_url; /* connect to this server */
//asf_stream_t stream[128]; /* in asf never more than 1->127 streams */
asf_header_t asfh;
off_t i_pos; /* position of next byte to be read */
/* */
uint8_t buffer_tcp[MMS_BUFFER_SIZE];
int i_buffer_tcp;
uint8_t buffer_udp[MMS_BUFFER_SIZE];
int i_buffer_udp;
/* data necessary to send data to server */
guid_t guid;
int i_command_level;
int i_seq_num;
uint32_t i_header_packet_id_type;
uint32_t i_media_packet_id_type;
int i_packet_seq_num;
uint8_t *p_cmd; /* latest command read */
int i_cmd; /* allocated at the begining */
uint8_t *p_header; /* allocated by mms_ReadPacket */
int i_header;
uint8_t *p_media; /* allocated by mms_ReadPacket */
size_t i_media;
size_t i_media_used;
/* extracted informations */
int i_command;
int i_eos;
/* from 0x01 answer (not yet set) */
char *psz_server_version;
char *psz_tool_version;
char *psz_update_player_url;
char *psz_encryption_type;
/* from 0x06 answer */
uint32_t i_flags_broadcast;
uint32_t i_media_length;
size_t i_packet_length;
uint32_t i_packet_count;
int i_max_bit_rate;
int i_header_size;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment