input.c 20.1 KB
Newer Older
1 2
/*****************************************************************************
 * input.c: input thread
Michel Kaempf's avatar
Michel Kaempf committed
3 4
 * Read an MPEG2 stream, demultiplex and parse it before sending it to
 * decoders.
5 6
 *****************************************************************************
 * Copyright (C) 1998, 1999, 2000 VideoLAN
7
 * $Id: input.c,v 1.92 2001/03/11 19:00:18 henri Exp $
8
 *
9
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
10 11 12 13 14
 *
 * 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.
15
 * 
16 17
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
20
 *
21 22 23
 * 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.
24
 *****************************************************************************/
Michel Kaempf's avatar
Michel Kaempf committed
25

26
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
27
 * Preamble
28
 *****************************************************************************/
29 30
#include "defs.h"

31 32 33 34 35 36 37
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
Michel Kaempf's avatar
Michel Kaempf committed
38

39 40
/* Network functions */

41
#ifndef SYS_BEOS
42 43 44 45 46 47
#include <netdb.h>                                             /* hostent ... */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
48
#endif
49

50 51 52 53
#ifdef STATS
#   include <sys/times.h>
#endif

Michel Kaempf's avatar
Michel Kaempf committed
54
#include "config.h"
55 56
#include "common.h"
#include "threads.h"
Michel Kaempf's avatar
Michel Kaempf committed
57
#include "mtime.h"
58
#include "modules.h"
59

60
#include "intf_msg.h"
61
#include "intf_plst.h"
62

63 64 65
#include "stream_control.h"
#include "input_ext-intf.h"
#include "input_ext-dec.h"
Michel Lespinasse's avatar
Yop,  
Michel Lespinasse committed
66

67
#include "input.h"
68 69 70
#include "interface.h"

#include "main.h"
Michel Kaempf's avatar
Michel Kaempf committed
71

72 73 74
 /* #include <netutils.h> */


75
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
76
 * Local prototypes
77
 *****************************************************************************/
78 79 80 81 82
static void RunThread       ( input_thread_t *p_input );
static  int InitThread      ( input_thread_t *p_input );
static void ErrorThread     ( input_thread_t *p_input );
static void DestroyThread   ( input_thread_t *p_input );
static void EndThread       ( input_thread_t *p_input );
Michel Kaempf's avatar
Michel Kaempf committed
83

84
/*****************************************************************************
85
 * input_CreateThread: creates a new input thread
86
 *****************************************************************************
87 88 89 90
 * This function creates a new input, and returns a pointer
 * to its description. On error, it returns NULL.
 * If pi_status is NULL, then the function will block until the thread is ready.
 * If not, it will be updated using one of the THREAD_* constants.
91
 *****************************************************************************/
92
input_thread_t *input_CreateThread ( playlist_item_t *p_item, int *pi_status )
Michel Kaempf's avatar
Michel Kaempf committed
93
{
94 95 96
    input_thread_t *    p_input;                        /* thread descriptor */
    int                 i_status;                           /* thread status */

97 98 99
    /* Allocate descriptor */
    p_input = (input_thread_t *)malloc( sizeof(input_thread_t) );
    if( p_input == NULL )
Michel Kaempf's avatar
Michel Kaempf committed
100
    {
101 102
        intf_ErrMsg( "input error: can't allocate input thread (%s)",
                     strerror(errno) );
Michel Kaempf's avatar
Michel Kaempf committed
103 104
        return( NULL );
    }
105

106 107 108
    /* Packets read once */
    p_input->i_read_once = INPUT_READ_ONCE;

109 110 111
    /* Initialize thread properties */
    p_input->b_die              = 0;
    p_input->b_error            = 0;
112 113 114 115 116
    p_input->b_eof              = 0;

    /* Set target */
    p_input->p_source           = p_item->psz_name;

117
    /* I have never understood that stuff --Meuuh */
118 119
    p_input->pi_status          = (pi_status != NULL) ? pi_status : &i_status;
    *p_input->pi_status         = THREAD_CREATE;
Michel Kaempf's avatar
Michel Kaempf committed
120

121
    /* Initialize stream description */
122 123
    p_input->stream.i_es_number = 0;
    p_input->stream.i_selected_es_number = 0;
124
    p_input->stream.i_pgrm_number = 0;
125
    p_input->stream.i_new_status = p_input->stream.i_new_rate = 0;
Christophe Massiot's avatar
Christophe Massiot committed
126
    p_input->stream.i_mux_rate = 0;
Michel Kaempf's avatar
Michel Kaempf committed
127

128 129 130 131
    p_input->stream.i_area_nb = 0;
    p_input->stream.pp_areas = NULL;
    /* By default there is one areas in a stream */
    input_AddArea( p_input );
132 133
    p_input->stream.p_selected_area = p_input->stream.pp_areas[0];
    p_input->stream.p_selected_area->i_seek = NO_SEEK;
134

135 136 137 138 139
    /* Initialize stream control properties. */
    p_input->stream.control.i_status = PLAYING_S;
    p_input->stream.control.i_rate = DEFAULT_RATE;
    p_input->stream.control.b_mute = 0;
    p_input->stream.control.b_bw = 0;
Michel Kaempf's avatar
Michel Kaempf committed
140

141 142
    /* Initialize default settings for spawned decoders */
    p_input->p_default_aout = p_main->p_aout;
Sam Hocevar's avatar
Sam Hocevar committed
143
    p_input->p_default_vout = p_main->p_vout;
144

Michel Kaempf's avatar
Michel Kaempf committed
145
    /* Create thread and set locks. */
146
    vlc_mutex_init( &p_input->stream.stream_lock );
147
    vlc_cond_init( &p_input->stream.stream_wait );
148 149 150
    vlc_mutex_init( &p_input->stream.control.control_lock );
    if( vlc_thread_create( &p_input->thread_id, "input", (void *) RunThread,
                           (void *) p_input ) )
Michel Kaempf's avatar
Michel Kaempf committed
151
    {
152 153
        intf_ErrMsg( "input error: can't create input thread (%s)",
                     strerror(errno) );
Michel Kaempf's avatar
Michel Kaempf committed
154 155 156
        free( p_input );
        return( NULL );
    }
157

158 159 160 161
    /* If status is NULL, wait until the thread is created */
    if( pi_status == NULL )
    {
        do
162
        {
163
            msleep( THREAD_SLEEP );
164
        } while( (i_status != THREAD_READY) && (i_status != THREAD_ERROR)
165
                && (i_status != THREAD_FATAL) );
166 167
        if( i_status != THREAD_READY )
        {
168 169
            return( NULL );
        }
170
    }
Michel Kaempf's avatar
Michel Kaempf committed
171 172 173
    return( p_input );
}

174
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
175
 * input_DestroyThread: mark an input thread as zombie
176
 *****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
177
 * This function should not return until the thread is effectively cancelled.
178
 *****************************************************************************/
179
void input_DestroyThread( input_thread_t *p_input, int *pi_status )
Michel Kaempf's avatar
Michel Kaempf committed
180
{
181
    int         i_status;                                   /* thread status */
182 183 184

    /* Set status */
    p_input->pi_status = (pi_status != NULL) ? pi_status : &i_status;
185 186
    *p_input->pi_status = THREAD_DESTROY;

187 188
    /* Request thread destruction */
    p_input->b_die = 1;
Michel Kaempf's avatar
Michel Kaempf committed
189

190 191 192 193 194
    /* Make the thread exit of an eventual vlc_cond_wait() */
    vlc_mutex_lock( &p_input->stream.stream_lock );
    vlc_cond_signal( &p_input->stream.stream_wait );
    vlc_mutex_unlock( &p_input->stream.stream_lock );

195 196 197 198 199 200
    /* If status is NULL, wait until thread has been destroyed */
    if( pi_status == NULL )
    {
        do
        {
            msleep( THREAD_SLEEP );
201 202
        } while ( (i_status != THREAD_OVER) && (i_status != THREAD_ERROR)
                  && (i_status != THREAD_FATAL) );
203
    }
Michel Kaempf's avatar
Michel Kaempf committed
204 205
}

206
/*****************************************************************************
207
 * RunThread: main thread loop
208
 *****************************************************************************
209
 * Thread in charge of processing the network packets and demultiplexing.
210
 *****************************************************************************/
211
static void RunThread( input_thread_t *p_input )
Michel Kaempf's avatar
Michel Kaempf committed
212
{
213
    int                     i_error, i;
Michel Kaempf's avatar
Michel Kaempf committed
214

215 216 217 218 219 220 221 222 223 224
    if( InitThread( p_input ) )
    {

        /* If we failed, wait before we are killed, and exit */
        *p_input->pi_status = THREAD_ERROR;
        p_input->b_error = 1;
        ErrorThread( p_input );
        DestroyThread( p_input );
        return;
    }
Michel Kaempf's avatar
Michel Kaempf committed
225

226
    while( !p_input->b_die && !p_input->b_error && !p_input->b_eof )
227
    {
228
        data_packet_t *         pp_packets[p_input->i_read_once];
229

230
#ifdef STATS
231
        p_input->c_loops++;
232 233
#endif

234
        vlc_mutex_lock( &p_input->stream.stream_lock );
235
        if( p_input->stream.p_selected_area->i_seek != NO_SEEK )
236
        {
237 238
            if( p_input->stream.b_seekable && p_input->pf_seek != NULL )
            {
239 240
                p_input->pf_seek( p_input,
                                  p_input->stream.p_selected_area->i_seek );
241 242 243 244 245 246 247 248 249 250 251 252 253

                for( i = 0; i < p_input->stream.i_pgrm_number; i++ )
                {
                    pgrm_descriptor_t * p_pgrm
                                            = p_input->stream.pp_programs[i];
                    /* Escape all decoders for the stream discontinuity they
                     * will encounter. */
                    input_EscapeDiscontinuity( p_input, p_pgrm );

                    /* Reinitialize synchro. */
                    p_pgrm->i_synchro_state = SYNCHRO_REINIT;
                }
            }
254
            p_input->stream.p_selected_area->i_seek = NO_SEEK;
255
        }
256
        vlc_mutex_unlock( &p_input->stream.stream_lock );
257 258

        i_error = p_input->pf_read( p_input, pp_packets );
259

260
        /* Demultiplex read packets. */
261
        for( i = 0; i < p_input->i_read_once && pp_packets[i] != NULL; i++ )
262 263 264
        {
            p_input->pf_demux( p_input, pp_packets[i] );
        }
265

266 267 268
        if( i_error )
        {
            if( i_error == 1 )
269
            {
270 271 272 273
                /* End of file - we do not set b_die because only the
                 * interface is allowed to do so. */
                intf_WarnMsg( 1, "End of file reached" );
                p_input->b_eof = 1;
274
            }
275
            else
276
            {
277
                p_input->b_error = 1;
278
            }
279 280 281
        }
    }

282
    if( p_input->b_error || p_input->b_eof )
283 284 285
    {
        ErrorThread( p_input );
    }
286

287
    EndThread( p_input );
288 289 290

    DestroyThread( p_input );

291
    intf_DbgMsg("Thread end");
292 293
}

294
/*****************************************************************************
295
 * InitThread: init the input Thread
296
 *****************************************************************************/
297
static int InitThread( input_thread_t * p_input )
Michel Kaempf's avatar
Michel Kaempf committed
298 299 300
{

#ifdef STATS
301 302 303 304 305 306
    /* Initialize statistics */
    p_input->c_loops                    = 0;
    p_input->c_bytes                    = 0;
    p_input->c_payload_bytes            = 0;
    p_input->c_packets_read             = 0;
    p_input->c_packets_trashed          = 0;
Michel Kaempf's avatar
Michel Kaempf committed
307
#endif
308

Sam Hocevar's avatar
Sam Hocevar committed
309
    p_input->p_input_module = module_Need( p_main->p_bank,
310 311
                                           MODULE_CAPABILITY_INPUT,
                                           (probedata_t *)p_input );
312 313

    if( p_input->p_input_module == NULL )
314
    {
315
        intf_ErrMsg( "input error: no suitable input module" );
316
        return( -1 );
Michel Kaempf's avatar
Michel Kaempf committed
317
    }
318

319 320 321 322 323 324
#define f p_input->p_input_module->p_functions->input.functions.input
    p_input->pf_init          = f.pf_init;
    p_input->pf_open          = f.pf_open;
    p_input->pf_close         = f.pf_close;
    p_input->pf_end           = f.pf_end;
    p_input->pf_read          = f.pf_read;
325
    p_input->pf_set_area      = f.pf_set_area;
326 327 328 329 330 331 332 333 334
    p_input->pf_demux         = f.pf_demux;
    p_input->pf_new_packet    = f.pf_new_packet;
    p_input->pf_new_pes       = f.pf_new_pes;
    p_input->pf_delete_packet = f.pf_delete_packet;
    p_input->pf_delete_pes    = f.pf_delete_pes;
    p_input->pf_rewind        = f.pf_rewind;
    p_input->pf_seek          = f.pf_seek;
#undef f
    p_input->pf_open( p_input );
335

336
    if( p_input->b_error )
337
    {
338 339
        /* We barfed -- exit nicely */
        p_input->pf_close( p_input );
Sam Hocevar's avatar
Sam Hocevar committed
340
        module_Unneed( p_main->p_bank, p_input->p_input_module );
341
        return( -1 );
342
    }
343 344

    p_input->pf_init( p_input );
345

346
    *p_input->pi_status = THREAD_READY;
347 348

    return( 0 );
Michel Kaempf's avatar
Michel Kaempf committed
349 350
}

351
/*****************************************************************************
352
 * ErrorThread: RunThread() error loop
353
 *****************************************************************************
354
 * This function is called when an error occured during thread main's loop.
355
 *****************************************************************************/
356
static void ErrorThread( input_thread_t *p_input )
Michel Kaempf's avatar
Michel Kaempf committed
357
{
358
    while( !p_input->b_die )
Michel Kaempf's avatar
Michel Kaempf committed
359
    {
360 361
        /* Sleep a while */
        msleep( INPUT_IDLE_SLEEP );
Michel Kaempf's avatar
Michel Kaempf committed
362 363 364
    }
}

365
/*****************************************************************************
366
 * EndThread: end the input thread
367
 *****************************************************************************/
368
static void EndThread( input_thread_t * p_input )
369
{
370
    int *       pi_status;                                  /* thread status */
371

372 373 374
    /* Store status */
    pi_status = p_input->pi_status;
    *pi_status = THREAD_END;
375

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
#ifdef STATS
    {
        struct tms cpu_usage;
        times( &cpu_usage );

        intf_Msg("input stats: cpu usage (user: %d, system: %d)",
                 cpu_usage.tms_utime, cpu_usage.tms_stime);
    }
#endif

    /* Free all ES and destroy all decoder threads */
    input_EndStream( p_input );

    /* Free demultiplexer's data */
    p_input->pf_end( p_input );

392 393 394
    /* Close stream */
    p_input->pf_close( p_input );

395
    /* Release modules */
Sam Hocevar's avatar
Sam Hocevar committed
396
    module_Unneed( p_main->p_bank, p_input->p_input_module );
397

398 399 400 401 402 403 404 405 406 407 408
}

/*****************************************************************************
 * DestroyThread: destroy the input thread
 *****************************************************************************/
static void DestroyThread( input_thread_t * p_input )
{
    int *       pi_status;                                  /* thread status */

    /* Store status */
    pi_status = p_input->pi_status;
409

410 411
    /* Destroy Mutex locks */
    vlc_mutex_destroy( &p_input->stream.control.control_lock );
412
    vlc_mutex_destroy( &p_input->stream.stream_lock );
413
    
414
    /* Free input structure */
415
    free( p_input );
416

417 418
    /* Update status */
    *pi_status = THREAD_OVER;
419
}
420

421
/*****************************************************************************
422
 * input_FileOpen : open a file descriptor
423
 *****************************************************************************/
424
void input_FileOpen( input_thread_t * p_input )
Michel Kaempf's avatar
Michel Kaempf committed
425
{
426
    struct stat         stat_info;
427 428 429
    int                 i_stat;

    char *psz_name = p_input->p_source;
Michel Kaempf's avatar
Michel Kaempf committed
430

431 432 433
    /* FIXME: this code ought to be in the plugin so that code can
     * be shared with the *_Probe function */
    if( ( i_stat = stat( psz_name, &stat_info ) ) == (-1) )
434
    {
435 436 437 438 439 440 441 442
        int i_size = strlen( psz_name );

        if( ( i_size > 4 )
            && !strncasecmp( psz_name, "dvd:", 4 ) )
        {
            /* get rid of the 'dvd:' stuff and try again */
            psz_name += 4;
            i_stat = stat( psz_name, &stat_info );
443
        }
444
        else if( ( i_size > 5 )
445 446 447 448 449
                 && !strncasecmp( psz_name, "file:", 5 ) )
        {
            /* get rid of the 'file:' stuff and try again */
            psz_name += 5;
            i_stat = stat( psz_name, &stat_info );
450
        }
451

452
        if( i_stat == (-1) )
453 454 455 456 457 458
        {
            intf_ErrMsg( "input error: cannot stat() file `%s' (%s)",
                         psz_name, strerror(errno));
            p_input->b_error = 1;
            return;
        }
459 460 461 462 463 464 465 466 467 468 469
    }

    vlc_mutex_lock( &p_input->stream.stream_lock );

    /* If we are here we can control the pace... */
    p_input->stream.b_pace_control = 1;

    if( S_ISREG(stat_info.st_mode) || S_ISCHR(stat_info.st_mode)
         || S_ISBLK(stat_info.st_mode) )
    {
        p_input->stream.b_seekable = 1;
470
        p_input->stream.p_selected_area->i_size = stat_info.st_size;
471
    }
472 473 474 475 476
    else if( S_ISFIFO(stat_info.st_mode)
#ifndef SYS_BEOS
             || S_ISSOCK(stat_info.st_mode)
#endif
             )
477
    {
478
        p_input->stream.b_seekable = 0;
479
        p_input->stream.p_selected_area->i_size = 0;
Benoit Steiner's avatar
Benoit Steiner committed
480 481 482
    }
    else
    {
483
        vlc_mutex_unlock( &p_input->stream.stream_lock );
484
        intf_ErrMsg( "input error: unknown file type for `%s'",
485
                     psz_name );
486 487 488
        p_input->b_error = 1;
        return;
    }
489

490
    p_input->stream.p_selected_area->i_tell = 0;
491 492
    vlc_mutex_unlock( &p_input->stream.stream_lock );

493 494
    intf_Msg( "input: opening %s", p_input->p_source );
    if( (p_input->i_handle = open( psz_name,
495 496
                                   /*O_NONBLOCK | O_LARGEFILE*/0 )) == (-1) )
    {
497
        intf_ErrMsg( "input error: cannot open file (%s)", strerror(errno) );
498 499
        p_input->b_error = 1;
        return;
Michel Kaempf's avatar
Michel Kaempf committed
500 501 502
    }

}
Stéphane Borel's avatar
Stéphane Borel committed
503 504

/*****************************************************************************
505
 * input_FileClose : close a file descriptor
Stéphane Borel's avatar
Stéphane Borel committed
506
 *****************************************************************************/
507
void input_FileClose( input_thread_t * p_input )
Stéphane Borel's avatar
Stéphane Borel committed
508
{
509
    close( p_input->i_handle );
Stéphane Borel's avatar
Stéphane Borel committed
510

511
    return;
Stéphane Borel's avatar
Stéphane Borel committed
512
}
513

514

515
#ifndef SYS_BEOS
516
/*****************************************************************************
517
 * input_NetworkOpen : open a network socket 
518
 *****************************************************************************/
519
void input_NetworkOpen( input_thread_t * p_input )
520
{
521 522 523 524
 
    int                 i_option_value, i_port;
    struct sockaddr_in  s_socket;
    boolean_t           b_broadcast;
525
    
526
    /* FIXME : we don't handle channels for the moment */
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
    
    /* Get the remote server */
    if( p_input->p_source == NULL )
    {
        p_input->p_source = main_GetPszVariable( INPUT_SERVER_VAR, 
                                                 INPUT_SERVER_DEFAULT );
    }
    
    /* Open a SOCK_DGRAM (UDP) socket, in the AF_INET domain, automatic (0)
     * protocol */
    p_input->i_handle = socket( AF_INET, SOCK_DGRAM, 0 );
    if( p_input->i_handle == -1 )
    {
        intf_ErrMsg("NetworkOpen : can't create socket : %s", strerror(errno));
        p_input->b_error = 1;
        return;
    }

    /* We may want to reuse an already used socket */
    i_option_value = 1;
    if( setsockopt( p_input->i_handle, SOL_SOCKET, SO_REUSEADDR,
                    &i_option_value,sizeof( i_option_value ) ) == -1 )
    {
        intf_ErrMsg("NetworkOpen : can't configure socket (SO_REUSEADDR: %s)",
                    strerror(errno));
        close( p_input->i_handle );
        p_input->b_error = 1;
        return;
    }

    /* Increase the receive buffer size to 1/2MB (8Mb/s during 1/2s) to avoid
     * packet loss caused by scheduling problems */
    i_option_value = 524288;
    if( setsockopt( p_input->i_handle, SOL_SOCKET, SO_RCVBUF, &i_option_value,
                    sizeof( i_option_value ) ) == -1 )
    {
        intf_ErrMsg("NetworkOpen : can't configure socket (SO_RCVBUF: %s)", 
                    strerror(errno));
        close( p_input->i_handle );
        p_input->b_error = 1;
        return;
    }

    /* Get details about what we are supposed to do */
    b_broadcast = (boolean_t)main_GetIntVariable( INPUT_BROADCAST_VAR, 0 );
    i_port = main_GetIntVariable( INPUT_PORT_VAR, INPUT_PORT_DEFAULT );

    /* TODO : here deal with channel stufs */
575
    
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
    /* Build the local socket */
    if ( input_BuildLocalAddr( &s_socket, i_port, b_broadcast ) 
         == -1 )
    {
        close( p_input->i_handle );
        p_input->b_error = 1;
        return;
    }
    
    /* Bind it */
    if( bind( p_input->i_handle, (struct sockaddr *)&s_socket, 
              sizeof( s_socket ) ) < 0 )
    {
        intf_ErrMsg("NetworkOpen: can't bind socket (%s)", strerror(errno));
        close( p_input->i_handle );
        p_input->b_error = 1;
        return;
    }

    /* Build socket for remote connection */
596
    if ( input_BuildRemoteAddr( &s_socket, p_input->p_source ) 
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
         == -1 )
    {
        close( p_input->i_handle );
        p_input->b_error = 1;
        return;
    }

    /* And connect it ... should we really connect ? */
    if( connect( p_input->i_handle, (struct sockaddr *) &s_socket,
                 sizeof( s_socket ) ) == (-1) )
    {
        intf_ErrMsg("NetworkOpen: can't connect socket" );
        close( p_input->i_handle );
        p_input->b_error = 1;
        return;
    }

    /* We can't pace control, but FIXME : bug in meuuh's code to sync PCR
     * with the server. */
    p_input->stream.b_pace_control = 1;
    
    return;
}

/*****************************************************************************
 * input_NetworkClose : close a network socket
 *****************************************************************************/
void input_NetworkClose( input_thread_t * p_input )
{
    close( p_input->i_handle );
    /* FIXME: deal with channels */
}
629
#endif