f6ed6bb9c9757998d52a62a0f2fc6e4879eaaee8
[vlc.git] / src / input / input_programs.c
1 /*****************************************************************************
2  * input_programs.c: es_descriptor_t, pgrm_descriptor_t management
3  *****************************************************************************
4  * Copyright (C) 1999-2001 VideoLAN
5  * $Id: input_programs.c,v 1.85 2002/05/13 21:55:30 fenrir Exp $
6  *
7  * Authors: Christophe Massiot <massiot@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  * 
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28 #include <string.h>                                    /* memcpy(), memset() */
29 #include <sys/types.h>                                              /* off_t */
30
31 #include <videolan/vlc.h>
32
33 #include "stream_control.h"
34 #include "input_ext-intf.h"
35 #include "input_ext-dec.h"
36 #include "input_ext-plugins.h"
37
38 /*
39  * NOTICE : all of these functions expect you to have taken the lock on
40  * p_input->stream.lock
41  */
42
43 /*****************************************************************************
44  * input_InitStream: init the stream descriptor of the given input
45  *****************************************************************************/
46 int input_InitStream( input_thread_t * p_input, size_t i_data_len )
47 {
48
49     p_input->stream.i_stream_id = 0;
50
51     /* initialized to 0 since we don't give the signal to the interface
52      * before the end of input initialization */
53     p_input->stream.b_changed = 0;
54     p_input->stream.pp_es = NULL;
55     p_input->stream.pp_selected_es = NULL;
56     p_input->stream.p_removed_es = NULL;
57     p_input->stream.p_newly_selected_es = NULL;
58     p_input->stream.pp_programs = NULL;
59     p_input->stream.p_selected_program = NULL;
60     p_input->stream.p_new_program = NULL;
61     
62     if( i_data_len )
63     {
64         if ( (p_input->stream.p_demux_data = malloc( i_data_len )) == NULL )
65         {
66             intf_ErrMsg( "Unable to allocate memory in input_InitStream");
67             return 1;
68         }
69         memset( p_input->stream.p_demux_data, 0, i_data_len );
70     }
71     else
72     {
73         p_input->stream.p_demux_data = NULL;
74     }
75
76     return 0;
77 }
78
79 /*****************************************************************************
80  * input_EndStream: free all stream descriptors
81  *****************************************************************************/
82 void input_EndStream( input_thread_t * p_input )
83 {
84     /* Free all programs and associated ES, and associated decoders. */
85     while( p_input->stream.i_pgrm_number )
86     {
87         input_DelProgram( p_input, p_input->stream.pp_programs[0] );
88     }
89
90     /* Free standalone ES */
91     while( p_input->stream.i_es_number )
92     {
93         input_DelES( p_input, p_input->stream.pp_es[0] );
94     }
95
96     /* Free all areas */
97     while( p_input->stream.i_area_nb )
98     {
99         input_DelArea( p_input, p_input->stream.pp_areas[0] );
100     }
101
102     /* Free selected ES */
103     if( p_input->stream.pp_selected_es != NULL )
104     {
105         free( p_input->stream.pp_selected_es );
106     }
107     
108     if( p_input->stream.p_demux_data != NULL )
109     {
110         free( p_input->stream.p_demux_data );
111     }
112 }
113
114 /*****************************************************************************
115  * input_FindProgram: returns a pointer to a program described by its ID
116  *****************************************************************************/
117 pgrm_descriptor_t * input_FindProgram( input_thread_t * p_input, u16 i_pgrm_id )
118 {
119     int     i;
120
121     for( i = 0; i < p_input->stream.i_pgrm_number; i++ )
122     {
123         if( p_input->stream.pp_programs[i]->i_number == i_pgrm_id )
124         {
125             return p_input->stream.pp_programs[i];
126         }
127     }
128
129     return( NULL );
130 }
131
132 /*****************************************************************************
133  * input_AddProgram: add and init a program descriptor
134  *****************************************************************************
135  * This program descriptor will be referenced in the given stream descriptor
136  *****************************************************************************/
137 pgrm_descriptor_t * input_AddProgram( input_thread_t * p_input,
138                                       u16 i_pgrm_id, size_t i_data_len )
139 {
140     /* Where to add the pgrm */
141     int i_pgrm_index = p_input->stream.i_pgrm_number;
142
143     /* Add an entry to the list of program associated with the stream */
144     p_input->stream.i_pgrm_number++;
145     p_input->stream.pp_programs = realloc( p_input->stream.pp_programs,
146                                            p_input->stream.i_pgrm_number
147                                             * sizeof(pgrm_descriptor_t *) );
148     if( p_input->stream.pp_programs == NULL )
149     {
150         intf_ErrMsg( "Unable to realloc memory in input_AddProgram" );
151         return( NULL );
152     }
153     
154     /* Allocate the structure to store this description */
155     p_input->stream.pp_programs[i_pgrm_index] =
156                                         malloc( sizeof(pgrm_descriptor_t) );
157     if( p_input->stream.pp_programs[i_pgrm_index] == NULL )
158     {
159         intf_ErrMsg( "Unable to allocate memory in input_AddProgram" );
160         return( NULL );
161     }
162     
163     /* Init this entry */
164     p_input->stream.pp_programs[i_pgrm_index]->i_number = i_pgrm_id;
165     p_input->stream.pp_programs[i_pgrm_index]->b_is_ok = 0;
166     p_input->stream.pp_programs[i_pgrm_index]->i_version = 0;
167
168     p_input->stream.pp_programs[i_pgrm_index]->i_es_number = 0;
169     p_input->stream.pp_programs[i_pgrm_index]->pp_es = NULL;
170
171     input_ClockInit( p_input->stream.pp_programs[i_pgrm_index] );
172
173     p_input->stream.pp_programs[i_pgrm_index]->i_synchro_state
174                                                 = SYNCHRO_START;
175
176     if( i_data_len )
177     {
178         p_input->stream.pp_programs[i_pgrm_index]->p_demux_data =
179             malloc( i_data_len );
180         if( p_input->stream.pp_programs[i_pgrm_index]->p_demux_data == NULL )
181         {
182             intf_ErrMsg( "Unable to allocate memory in input_AddProgram" );
183             return( NULL );
184         }
185         memset( p_input->stream.pp_programs[i_pgrm_index]->p_demux_data, 0,
186                 i_data_len );
187     }
188     else
189     {
190         p_input->stream.pp_programs[i_pgrm_index]->p_demux_data = NULL;
191     }
192
193     return p_input->stream.pp_programs[i_pgrm_index];
194 }
195
196 /*****************************************************************************
197  * input_DelProgram: destroy a program descriptor
198  *****************************************************************************
199  * All ES descriptions referenced in the descriptor will be deleted.
200  *****************************************************************************/
201 void input_DelProgram( input_thread_t * p_input, pgrm_descriptor_t * p_pgrm )
202 {
203     int i_pgrm_index;
204
205     ASSERT( p_pgrm );
206
207     /* Free the structures that describe the es that belongs to that program */
208     while( p_pgrm->i_es_number )
209     {
210         input_DelES( p_input, p_pgrm->pp_es[0] );
211     }
212
213     /* Free the demux data */
214     if( p_pgrm->p_demux_data != NULL )
215     {
216         free( p_pgrm->p_demux_data );
217     }
218
219     /* Find the program in the programs table */
220     for( i_pgrm_index = 0; i_pgrm_index < p_input->stream.i_pgrm_number;
221          i_pgrm_index++ )
222     {
223         if( p_input->stream.pp_programs[i_pgrm_index] == p_pgrm )
224             break;
225     }
226
227     /* Remove this program from the stream's list of programs */
228     p_input->stream.i_pgrm_number--;
229
230     p_input->stream.pp_programs[i_pgrm_index] =
231         p_input->stream.pp_programs[p_input->stream.i_pgrm_number];
232     p_input->stream.pp_programs = realloc( p_input->stream.pp_programs,
233                                            p_input->stream.i_pgrm_number
234                                             * sizeof(pgrm_descriptor_t *) );
235
236     if( p_input->stream.i_pgrm_number && p_input->stream.pp_programs == NULL)
237     {
238         intf_ErrMsg( "input error: unable to realloc program list"
239                      " in input_DelProgram" );
240     }
241
242     /* Free the description of this program */
243     free( p_pgrm );
244 }
245
246 /*****************************************************************************
247  * input_AddArea: add and init an area descriptor
248  *****************************************************************************
249  * This area descriptor will be referenced in the given stream descriptor
250  *****************************************************************************/
251 input_area_t * input_AddArea( input_thread_t * p_input )
252 {
253     /* Where to add the pgrm */
254     int i_area_index = p_input->stream.i_area_nb;
255
256     /* Add an entry to the list of program associated with the stream */
257     p_input->stream.i_area_nb++;
258     p_input->stream.pp_areas = realloc( p_input->stream.pp_areas,
259                                         p_input->stream.i_area_nb
260                                             * sizeof(input_area_t *) );
261     if( p_input->stream.pp_areas == NULL )
262     {
263         intf_ErrMsg( "Unable to realloc memory in input_AddArea" );
264         return( NULL );
265     }
266     
267     /* Allocate the structure to store this description */
268     p_input->stream.pp_areas[i_area_index] =
269                                         malloc( sizeof(input_area_t) );
270     if( p_input->stream.pp_areas[i_area_index] == NULL )
271     {
272         intf_ErrMsg( "Unable to allocate memory in input_AddArea" );
273         return( NULL );
274     }
275     
276     /* Init this entry */
277     p_input->stream.pp_areas[i_area_index]->i_id = 0;
278     p_input->stream.pp_areas[i_area_index]->i_start = 0;
279     p_input->stream.pp_areas[i_area_index]->i_size = 0;
280     p_input->stream.pp_areas[i_area_index]->i_tell = 0;
281     p_input->stream.pp_areas[i_area_index]->i_seek = NO_SEEK;
282     p_input->stream.pp_areas[i_area_index]->i_part_nb = 1;
283     p_input->stream.pp_areas[i_area_index]->i_part= 0;
284
285     return p_input->stream.pp_areas[i_area_index];
286 }
287
288 /*****************************************************************************
289  * input_SetProgram: changes the current program
290  *****************************************************************************/
291 int input_SetProgram( input_thread_t * p_input, pgrm_descriptor_t * p_new_prg )
292 {
293     int i_es_index;
294     int i_required_audio_es;
295     int i_required_spu_es;
296     int i_audio_es = 0;
297     int i_spu_es = 0;
298
299     if ( p_input->stream.p_selected_program )
300     {
301         for ( i_es_index = 1 ; /* 0 should be the PMT */
302                 i_es_index < p_input->stream.p_selected_program->
303                 i_es_number ;
304                 i_es_index ++ )
305         {
306 #define p_es p_input->stream.p_selected_program->pp_es[i_es_index]
307             if ( p_es->p_decoder_fifo ) /* if the ES was selected */
308             {
309                 input_UnselectES( p_input , p_es );
310             }
311 #undef p_es
312         }
313     }
314     /* Get the number of the required audio stream */
315     if( p_main->b_audio )
316     {
317         /* Default is the first one */
318         i_required_audio_es = config_GetIntVariable( "audio-channel" );
319         if( i_required_audio_es < 0 )
320         {
321             i_required_audio_es = 1;
322         }
323     }
324     else
325     {
326         i_required_audio_es = 0;
327     }
328
329     /* Same thing for subtitles */
330     if( p_main->b_video )
331     {
332         /* for spu, default is none */
333         i_required_spu_es = config_GetIntVariable( "spu-channel" );
334         if( i_required_spu_es < 0 )
335         {
336             i_required_spu_es = 0;
337         }
338     }
339     else
340     {
341         i_required_spu_es = 0;
342     }
343
344     for (i_es_index = 0 ; i_es_index < p_new_prg->i_es_number ; i_es_index ++ )
345     {
346         switch( p_new_prg->pp_es[i_es_index]->i_cat )
347         {
348             case VIDEO_ES:
349                 intf_WarnMsg( 4, "Selecting ES %x",
350                             p_new_prg->pp_es[i_es_index]->i_id );
351                 input_SelectES( p_input, p_new_prg->pp_es[i_es_index] );
352                 break;
353             case AUDIO_ES:
354                 i_audio_es += 1;
355                 if( i_audio_es <= i_required_audio_es )
356                 {
357                     intf_WarnMsg( 4, "Selecting ES %x",
358                                 p_new_prg->pp_es[i_es_index]->i_id );
359                     input_SelectES( p_input, p_new_prg->pp_es[i_es_index]);
360                 }
361                 break;
362             /* Not sure this one is fully specification-compliant */
363             case SPU_ES :
364                 i_spu_es += 1;
365                 if( i_spu_es <= i_required_spu_es )
366                 {
367                     intf_WarnMsg( 4, "Selecting ES %x",
368                                 p_new_prg->pp_es[i_es_index]->i_id );
369                     input_SelectES( p_input, p_new_prg->pp_es[i_es_index] );
370                 }
371             break;
372             default :
373                 intf_WarnMsg( 2, "ES %x has unknown type",
374                             p_new_prg->pp_es[i_es_index]->i_id );
375                 break;
376         }
377
378     }
379
380
381     p_input->stream.p_selected_program = p_new_prg;
382
383     return( 0 );
384 }
385
386
387 /*****************************************************************************
388  * input_DelArea: destroy a area descriptor
389  *****************************************************************************
390  * All ES descriptions referenced in the descriptor will be deleted.
391  *****************************************************************************/
392 void input_DelArea( input_thread_t * p_input, input_area_t * p_area )
393 {
394     int i_area_index;
395
396     ASSERT( p_area );
397
398     /* Find the area in the areas table */
399     for( i_area_index = 0; i_area_index < p_input->stream.i_area_nb;
400          i_area_index++ )
401     {
402         if( p_input->stream.pp_areas[i_area_index] == p_area )
403             break;
404     }
405
406     /* Remove this area from the stream's list of areas */
407     p_input->stream.i_area_nb--;
408
409     p_input->stream.pp_areas[i_area_index] =
410         p_input->stream.pp_areas[p_input->stream.i_area_nb];
411     p_input->stream.pp_areas = realloc( p_input->stream.pp_areas,
412                                            p_input->stream.i_area_nb
413                                             * sizeof(input_area_t *) );
414
415     if( p_input->stream.i_area_nb && p_input->stream.pp_areas == NULL)
416     {
417         intf_ErrMsg( "input error: unable to realloc area list"
418                      " in input_DelArea" );
419     }
420
421     /* Free the description of this area */
422     free( p_area );
423 }
424
425
426 /*****************************************************************************
427  * input_FindES: returns a pointer to an ES described by its ID
428  *****************************************************************************/
429 es_descriptor_t * input_FindES( input_thread_t * p_input, u16 i_es_id )
430 {
431     int     i;
432
433     for( i = 0; i < p_input->stream.i_es_number; i++ )
434     {
435         if( p_input->stream.pp_es[i]->i_id == i_es_id )
436         {
437             return p_input->stream.pp_es[i];
438         }
439     }
440
441     return( NULL );
442 }
443
444 /*****************************************************************************
445  * input_AddES:
446  *****************************************************************************
447  * Reserve a slot in the table of ES descriptors for the ES and add it to the
448  * list of ES of p_pgrm. If p_pgrm if NULL, then the ES is considered as stand
449  * alone (PSI ?)
450  *****************************************************************************/
451 es_descriptor_t * input_AddES( input_thread_t * p_input,
452                                pgrm_descriptor_t * p_pgrm, u16 i_es_id,
453                                size_t i_data_len )
454 {
455     es_descriptor_t * p_es;
456
457     p_es = (es_descriptor_t *)malloc( sizeof(es_descriptor_t) );
458     if( p_es == NULL )
459     {
460         intf_ErrMsg( "Unable to allocate memory in input_AddES" );
461         return( NULL);
462     }
463     p_input->stream.i_es_number++;
464     p_input->stream.pp_es = realloc( p_input->stream.pp_es,
465                                      p_input->stream.i_es_number
466                                       * sizeof(es_descriptor_t *) );
467     if( p_input->stream.pp_es == NULL )
468     {
469         intf_ErrMsg( "Unable to realloc memory in input_AddES" );
470         return( NULL );
471     }
472     p_input->stream.pp_es[p_input->stream.i_es_number - 1] = p_es;
473
474     /* Init its values */
475     p_es->i_id = i_es_id;
476     p_es->psz_desc[0] = '\0';
477     p_es->p_pes = NULL;
478     p_es->p_decoder_fifo = NULL;
479     p_es->b_audio = 0;
480     p_es->i_cat = UNKNOWN_ES;
481     p_es->i_demux_fd = 0;
482
483     if( i_data_len )
484     {
485         p_es->p_demux_data = malloc( i_data_len );
486         if( p_es->p_demux_data == NULL )
487         {
488             intf_ErrMsg( "Unable to allocate memory in input_AddES" );
489             return( NULL );
490         }
491         memset( p_es->p_demux_data, 0, i_data_len );
492     }
493     else
494     {
495         p_es->p_demux_data = NULL;
496     }
497
498     /* Add this ES to the program definition if one is given */
499     if( p_pgrm )
500     {
501         p_pgrm->i_es_number++;
502         p_pgrm->pp_es = realloc( p_pgrm->pp_es,
503                                  p_pgrm->i_es_number
504                                   * sizeof(es_descriptor_t *) );
505         if( p_pgrm->pp_es == NULL )
506         {
507             intf_ErrMsg( "Unable to realloc memory in input_AddES" );
508             return( NULL );
509         }
510         p_pgrm->pp_es[p_pgrm->i_es_number - 1] = p_es;
511         p_es->p_pgrm = p_pgrm;
512     }
513     else
514     {
515         p_es->p_pgrm = NULL;
516     }
517
518     return p_es;
519 }
520
521 /*****************************************************************************
522  * input_DelES:
523  *****************************************************************************/
524 void input_DelES( input_thread_t * p_input, es_descriptor_t * p_es )
525 {
526     int                     i_index, i_es_index;
527     pgrm_descriptor_t *     p_pgrm;
528
529     ASSERT( p_es );
530     p_pgrm = p_es->p_pgrm;
531
532     /* Kill associated decoder, if any. */
533     if( p_es->p_decoder_fifo != NULL )
534     {
535         input_EndDecoder( p_input, p_es );
536     }
537
538     /* Remove this ES from the description of the program if it is associated to
539      * one */
540     if( p_pgrm )
541     {
542         for( i_index = 0; i_index < p_pgrm->i_es_number; i_index++ )
543         {
544             if( p_pgrm->pp_es[i_index] == p_es )
545             {
546                 p_pgrm->i_es_number--;
547                 p_pgrm->pp_es[i_index] = p_pgrm->pp_es[p_pgrm->i_es_number];
548                 p_pgrm->pp_es = realloc( p_pgrm->pp_es,
549                                          p_pgrm->i_es_number
550                                           * sizeof(es_descriptor_t *));
551                 if( p_pgrm->i_es_number && p_pgrm->pp_es == NULL )
552                 {
553                     intf_ErrMsg( "Unable to realloc memory in input_DelES" );
554                 }
555                 break;
556             }
557         }
558     }
559
560     /* Free the demux data */
561     if( p_es->p_demux_data != NULL )
562     {
563         free( p_es->p_demux_data );
564     }
565
566     /* Find the ES in the ES table */
567     for( i_es_index = 0; i_es_index < p_input->stream.i_es_number;
568          i_es_index++ )
569     {
570         if( p_input->stream.pp_es[i_es_index] == p_es )
571             break;
572     }
573
574     /* Free the ES */
575     free( p_es );
576     p_input->stream.i_es_number--;
577     p_input->stream.pp_es[i_es_index] =
578                     p_input->stream.pp_es[p_input->stream.i_es_number];
579     p_input->stream.pp_es = realloc( p_input->stream.pp_es,
580                                      p_input->stream.i_es_number
581                                       * sizeof(es_descriptor_t *));
582     if( p_input->stream.i_es_number && p_input->stream.pp_es == NULL )
583     {
584         intf_ErrMsg( "Unable to realloc memory in input_DelES" );
585     }
586     
587 }
588
589 /*****************************************************************************
590  * input_SelectES: selects an ES and spawns the associated decoder
591  *****************************************************************************
592  * Remember we are still supposed to have stream_lock when entering this
593  * function ?
594  *****************************************************************************/
595 int input_SelectES( input_thread_t * p_input, es_descriptor_t * p_es )
596 {
597     if( p_es == NULL )
598     {
599         intf_ErrMsg( "input error: nothing to do in input_SelectES" );
600         return -1;
601     }
602
603     intf_WarnMsg( 4, "input: selecting ES 0x%x", p_es->i_id );
604
605     if( p_es->p_decoder_fifo != NULL )
606     {
607         intf_ErrMsg( "ES 0x%x is already selected", p_es->i_id );
608         return( -1 );
609     }
610
611     p_es->thread_id = 0;
612
613     switch( p_es->i_type )
614     {
615     case AC3_AUDIO_ES:
616     case MPEG1_AUDIO_ES:
617     case MPEG2_AUDIO_ES:
618     case LPCM_AUDIO_ES:
619         if( p_main->b_audio )
620         {
621             /* Release the lock, not to block the input thread during
622              * the creation of the thread. */
623             vlc_mutex_unlock( &p_input->stream.stream_lock );
624             p_es->thread_id = input_RunDecoder( p_input, p_es );
625             vlc_mutex_lock( &p_input->stream.stream_lock );
626         }
627         break;
628
629     case MPEG1_VIDEO_ES:
630     case MPEG2_VIDEO_ES:
631     case MPEG4_VIDEO_ES:
632     case MSMPEG4v1_VIDEO_ES:
633     case MSMPEG4v2_VIDEO_ES:
634     case MSMPEG4v3_VIDEO_ES:
635     case DVD_SPU_ES:
636         if( p_main->b_video )
637         {
638             /* Release the lock, not to block the input thread during
639              * the creation of the thread. */
640             vlc_mutex_unlock( &p_input->stream.stream_lock );
641             p_es->thread_id = input_RunDecoder( p_input, p_es );
642             vlc_mutex_lock( &p_input->stream.stream_lock );
643         }
644         break;
645
646     default:
647         intf_ErrMsg( "Unknown stream type 0x%x", p_es->i_type );
648         return( -1 );
649         break;
650     }
651
652     if( p_es->thread_id == 0 )
653     {
654         return( -1 );
655     }
656
657     return( 0 );
658 }
659
660 /*****************************************************************************
661  * input_UnselectES: removes an ES from the list of selected ES
662  *****************************************************************************/
663 int input_UnselectES( input_thread_t * p_input, es_descriptor_t * p_es )
664 {
665
666     int     i_index = 0;
667
668     if( p_es == NULL )
669     {
670         intf_ErrMsg( "Nothing to do in input_UnselectES" );
671         return -1;
672     }
673
674     intf_WarnMsg( 4, "input: unselecting ES 0x%x", p_es->i_id );
675
676     if( p_es->p_decoder_fifo == NULL )
677     {
678         intf_ErrMsg( "ES 0x%x is not selected", p_es->i_id );
679         return( -1 );
680     }
681
682     input_EndDecoder( p_input, p_es );
683     p_es->p_pes = NULL;
684
685     if( ( p_es->p_decoder_fifo == NULL ) &&
686         ( p_input->stream.i_selected_es_number > 0 ) )
687     {
688         p_input->stream.i_selected_es_number--;
689
690         while( ( i_index < p_input->stream.i_selected_es_number ) &&
691                ( p_input->stream.pp_selected_es[i_index] != p_es ) )
692         {
693             i_index++;
694         }
695
696         p_input->stream.pp_selected_es[i_index] = 
697           p_input->stream.pp_selected_es[p_input->stream.i_selected_es_number];
698
699         p_input->stream.pp_selected_es = realloc(
700                                            p_input->stream.pp_selected_es,
701                                            p_input->stream.i_selected_es_number
702                                             * sizeof(es_descriptor_t *) );
703
704         if( p_input->stream.pp_selected_es == NULL )
705         {
706             intf_WarnMsg( 4, "input: no more selected ES in input_UnselectES" );
707             return( 1 );
708         }
709     }
710
711     return( 0 );
712 }