Commit 711160b2 authored by Sam Hocevar's avatar Sam Hocevar

* ./src/audio_output/output.c: added an argument to aout_OutputNextBuffer

    which lets the audio output module give a timeout value for buffer
    delivery. This fixes a few issues with aout modules which were calling
    aout_OutputNextBuffer way too early.
parent d3bdbb2b
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* aout_internal.h : internal defines for audio output * aout_internal.h : internal defines for audio output
***************************************************************************** *****************************************************************************
* Copyright (C) 2002 VideoLAN * Copyright (C) 2002 VideoLAN
* $Id: aout_internal.h,v 1.10 2002/08/21 23:19:58 sam Exp $ * $Id: aout_internal.h,v 1.11 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Christophe Massiot <massiot@via.ecp.fr> * Authors: Christophe Massiot <massiot@via.ecp.fr>
* *
...@@ -211,7 +211,7 @@ int aout_OutputNew( aout_instance_t * p_aout, ...@@ -211,7 +211,7 @@ int aout_OutputNew( aout_instance_t * p_aout,
audio_sample_format_t * p_format ); audio_sample_format_t * p_format );
void aout_OutputPlay( aout_instance_t * p_aout, aout_buffer_t * p_buffer ); void aout_OutputPlay( aout_instance_t * p_aout, aout_buffer_t * p_buffer );
void aout_OutputDelete( aout_instance_t * p_aout ); void aout_OutputDelete( aout_instance_t * p_aout );
VLC_EXPORT( aout_buffer_t *, aout_OutputNextBuffer, ( aout_instance_t *, mtime_t, vlc_bool_t ) ); VLC_EXPORT( aout_buffer_t *, aout_OutputNextBuffer, ( aout_instance_t *, mtime_t, mtime_t, vlc_bool_t ) );
void aout_FormatPrepare( audio_sample_format_t * p_format ); void aout_FormatPrepare( audio_sample_format_t * p_format );
void aout_FifoInit( aout_instance_t *, aout_fifo_t *, u32 ); void aout_FifoInit( aout_instance_t *, aout_fifo_t *, u32 );
......
...@@ -4,7 +4,7 @@ struct module_symbols_t ...@@ -4,7 +4,7 @@ struct module_symbols_t
{ {
aout_buffer_t * (* aout_BufferNew_inner) ( aout_instance_t *, aout_input_t *, size_t ) ; aout_buffer_t * (* aout_BufferNew_inner) ( aout_instance_t *, aout_input_t *, size_t ) ;
aout_buffer_t * (* aout_FifoPop_inner) ( aout_instance_t * p_aout, aout_fifo_t * p_fifo ) ; aout_buffer_t * (* aout_FifoPop_inner) ( aout_instance_t * p_aout, aout_fifo_t * p_fifo ) ;
aout_buffer_t * (* aout_OutputNextBuffer_inner) ( aout_instance_t *, mtime_t, vlc_bool_t ) ; aout_buffer_t * (* aout_OutputNextBuffer_inner) ( aout_instance_t *, mtime_t, mtime_t, vlc_bool_t ) ;
aout_input_t * (* __aout_InputNew_inner) ( vlc_object_t *, aout_instance_t **, audio_sample_format_t * ) ; aout_input_t * (* __aout_InputNew_inner) ( vlc_object_t *, aout_instance_t **, audio_sample_format_t * ) ;
aout_instance_t * (* __aout_NewInstance_inner) ( vlc_object_t * ) ; aout_instance_t * (* __aout_NewInstance_inner) ( vlc_object_t * ) ;
char * (* __config_GetPsz_inner) (vlc_object_t *, const char *) ; char * (* __config_GetPsz_inner) (vlc_object_t *, const char *) ;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* alsa.c : alsa plugin for vlc * alsa.c : alsa plugin for vlc
***************************************************************************** *****************************************************************************
* Copyright (C) 2000-2001 VideoLAN * Copyright (C) 2000-2001 VideoLAN
* $Id: alsa.c,v 1.6 2002/08/19 23:07:30 sam Exp $ * $Id: alsa.c,v 1.7 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Henri Fallon <henri@videolan.org> - Original Author * Authors: Henri Fallon <henri@videolan.org> - Original Author
* Jeffrey Baker <jwbaker@acm.org> - Port to ALSA 1.0 API * Jeffrey Baker <jwbaker@acm.org> - Port to ALSA 1.0 API
...@@ -473,7 +473,7 @@ static void ALSAFill( aout_instance_t * p_aout ) ...@@ -473,7 +473,7 @@ static void ALSAFill( aout_instance_t * p_aout )
snd_pcm_status_get_tstamp( p_status, &ts_next ); snd_pcm_status_get_tstamp( p_status, &ts_next );
next_date = (mtime_t)ts_next.tv_sec * 1000000 + ts_next.tv_usec; next_date = (mtime_t)ts_next.tv_sec * 1000000 + ts_next.tv_usec;
p_buffer = aout_OutputNextBuffer( p_aout, next_date, p_buffer = aout_OutputNextBuffer( p_aout, next_date, 0,
p_sys->b_can_sleek ); p_sys->b_can_sleek );
/* Audio output buffer shortage -> stop the fill process and /* Audio output buffer shortage -> stop the fill process and
......
...@@ -205,7 +205,8 @@ static int aRtsThread( aout_instance_t * p_aout ) ...@@ -205,7 +205,8 @@ static int aRtsThread( aout_instance_t * p_aout )
/* Get the presentation date of the next write() operation. It /* Get the presentation date of the next write() operation. It
* is equal to the current date + latency */ * is equal to the current date + latency */
p_buffer = aout_OutputNextBuffer( p_aout, mdate() + p_sys->latency, 0 ); p_buffer = aout_OutputNextBuffer( p_aout, mdate() + p_sys->latency,
0, VLC_FALSE );
if ( p_buffer != NULL ) if ( p_buffer != NULL )
{ {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* esd.c : EsounD module * esd.c : EsounD module
***************************************************************************** *****************************************************************************
* Copyright (C) 2000, 2001 VideoLAN * Copyright (C) 2000, 2001 VideoLAN
* $Id: esd.c,v 1.8 2002/08/19 23:07:30 sam Exp $ * $Id: esd.c,v 1.9 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Samuel Hocevar <sam@zoy.org> * Authors: Samuel Hocevar <sam@zoy.org>
* *
...@@ -149,12 +149,10 @@ static int SetFormat( aout_instance_t *p_aout ) ...@@ -149,12 +149,10 @@ static int SetFormat( aout_instance_t *p_aout )
(mtime_t)( esd_get_latency( esd_open_sound(NULL) ) + ESD_BUF_SIZE/2 (mtime_t)( esd_get_latency( esd_open_sound(NULL) ) + ESD_BUF_SIZE/2
* p_aout->output.output.i_bytes_per_frame * p_aout->output.output.i_bytes_per_frame
* p_aout->output.output.i_rate * p_aout->output.output.i_rate
/ p_aout->output.output.i_frame_length
/ ESD_DEFAULT_RATE ) / ESD_DEFAULT_RATE )
* (mtime_t)1000000 * (mtime_t)1000000
/ p_aout->output.output.i_bytes_per_frame / p_aout->output.output.i_bytes_per_frame
/ p_aout->output.output.i_rate / p_aout->output.output.i_rate;
* p_aout->output.output.i_frame_length;
p_sys->b_initialized = VLC_TRUE; p_sys->b_initialized = VLC_TRUE;
...@@ -204,7 +202,8 @@ static int ESDThread( aout_instance_t * p_aout ) ...@@ -204,7 +202,8 @@ static int ESDThread( aout_instance_t * p_aout )
/* Get the presentation date of the next write() operation. It /* Get the presentation date of the next write() operation. It
* is equal to the current date + buffered samples + esd latency */ * is equal to the current date + buffered samples + esd latency */
p_buffer = aout_OutputNextBuffer( p_aout, mdate() + p_sys->latency, 0 ); p_buffer = aout_OutputNextBuffer( p_aout, mdate() + p_sys->latency,
0, VLC_FALSE );
if ( p_buffer != NULL ) if ( p_buffer != NULL )
{ {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* oss.c : OSS /dev/dsp module for vlc * oss.c : OSS /dev/dsp module for vlc
***************************************************************************** *****************************************************************************
* Copyright (C) 2000-2002 VideoLAN * Copyright (C) 2000-2002 VideoLAN
* $Id: oss.c,v 1.14 2002/08/24 01:14:29 sigmunau Exp $ * $Id: oss.c,v 1.15 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Michel Kaempf <maxx@via.ecp.fr> * Authors: Michel Kaempf <maxx@via.ecp.fr>
* Samuel Hocevar <sam@zoy.org> * Samuel Hocevar <sam@zoy.org>
...@@ -65,7 +65,7 @@ struct aout_sys_t ...@@ -65,7 +65,7 @@ struct aout_sys_t
volatile vlc_bool_t b_initialized; volatile vlc_bool_t b_initialized;
}; };
#define FRAME_SIZE 2048 #define FRAME_SIZE 1024
#define A52_FRAME_NB 1536 #define A52_FRAME_NB 1536
/***************************************************************************** /*****************************************************************************
...@@ -86,6 +86,7 @@ vlc_module_begin(); ...@@ -86,6 +86,7 @@ vlc_module_begin();
add_file( "dspdev", "/dev/dsp", NULL, N_("OSS dsp device"), NULL ); add_file( "dspdev", "/dev/dsp", NULL, N_("OSS dsp device"), NULL );
set_description( _("Linux OSS /dev/dsp module") ); set_description( _("Linux OSS /dev/dsp module") );
set_capability( "audio output", 100 ); set_capability( "audio output", 100 );
add_shortcut( "dsp" );
set_callbacks( Open, Close ); set_callbacks( Open, Close );
vlc_module_end(); vlc_module_end();
...@@ -258,7 +259,9 @@ static void Close( vlc_object_t * p_this ) ...@@ -258,7 +259,9 @@ static void Close( vlc_object_t * p_this )
p_aout->b_die = 1; p_aout->b_die = 1;
vlc_thread_join( p_aout ); vlc_thread_join( p_aout );
ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL );
close( p_sys->i_fd ); close( p_sys->i_fd );
free( p_sys ); free( p_sys );
} }
...@@ -305,22 +308,20 @@ static int OSSThread( aout_instance_t * p_aout ) ...@@ -305,22 +308,20 @@ static int OSSThread( aout_instance_t * p_aout )
if ( p_aout->output.output.i_format != AOUT_FMT_SPDIF ) if ( p_aout->output.output.i_format != AOUT_FMT_SPDIF )
{ {
mtime_t next_date = 0; mtime_t buffered = (mtime_t)GetBufInfo( p_aout ) * 1000000
/* Get the presentation date of the next write() operation. It
* is equal to the current date + duration of buffered samples.
* Order is important here, since GetBufInfo is believed to take
* more time than mdate(). */
next_date = (mtime_t)GetBufInfo( p_aout ) * 100000
/ p_aout->output.output.i_bytes_per_frame / p_aout->output.output.i_bytes_per_frame
/ p_aout->output.output.i_rate / p_aout->output.output.i_rate
* p_aout->output.output.i_frame_length; * p_aout->output.output.i_frame_length;
next_date += mdate();
p_buffer = aout_OutputNextBuffer( p_aout, next_date, VLC_FALSE ); /* Next buffer will be played at mdate()+buffered, and we tell
* the audio output that it can wait for a new packet for
* buffered/2 microseconds. */
p_buffer = aout_OutputNextBuffer( p_aout, mdate() + buffered,
buffered / 2, VLC_FALSE );
} }
else else
{ {
p_buffer = aout_OutputNextBuffer( p_aout, 0, VLC_TRUE ); p_buffer = aout_OutputNextBuffer( p_aout, 0, 0, VLC_TRUE );
} }
if ( p_buffer != NULL ) if ( p_buffer != NULL )
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* sdl.c : SDL audio output plugin for vlc * sdl.c : SDL audio output plugin for vlc
***************************************************************************** *****************************************************************************
* Copyright (C) 2000-2002 VideoLAN * Copyright (C) 2000-2002 VideoLAN
* $Id: sdl.c,v 1.4 2002/08/21 22:41:59 massiot Exp $ * $Id: sdl.c,v 1.5 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Michel Kaempf <maxx@via.ecp.fr> * Authors: Michel Kaempf <maxx@via.ecp.fr>
* Samuel Hocevar <sam@zoy.org> * Samuel Hocevar <sam@zoy.org>
...@@ -149,8 +149,16 @@ static void Close ( vlc_object_t *p_this ) ...@@ -149,8 +149,16 @@ static void Close ( vlc_object_t *p_this )
static void SDLCallback( void * _p_aout, byte_t * p_stream, int i_len ) static void SDLCallback( void * _p_aout, byte_t * p_stream, int i_len )
{ {
aout_instance_t * p_aout = (aout_instance_t *)_p_aout; aout_instance_t * p_aout = (aout_instance_t *)_p_aout;
//static mtime_t old = 0;
//static mtime_t diff = 0;
//mtime_t foo = mdate();
aout_buffer_t * p_buffer;
//if(old) diff = (9 * diff + (foo-old))/10;
/* FIXME : take into account SDL latency instead of mdate() */ /* FIXME : take into account SDL latency instead of mdate() */
aout_buffer_t * p_buffer = aout_OutputNextBuffer( p_aout, mdate(), 1 ); p_buffer = aout_OutputNextBuffer( p_aout, mdate(), 0, VLC_TRUE );
//p_buffer = aout_OutputNextBuffer( p_aout, foo - diff, 0, VLC_TRUE );
//fprintf(stderr, "foo - old : %lli, diff : %lli\n", foo-old, diff);
//old=foo;
if ( i_len != FRAME_SIZE * sizeof(s16) if ( i_len != FRAME_SIZE * sizeof(s16)
* p_aout->output.output.i_channels ) * p_aout->output.output.i_channels )
...@@ -160,11 +168,14 @@ static void SDLCallback( void * _p_aout, byte_t * p_stream, int i_len ) ...@@ -160,11 +168,14 @@ static void SDLCallback( void * _p_aout, byte_t * p_stream, int i_len )
if ( p_buffer != NULL ) if ( p_buffer != NULL )
{ {
//fprintf(stderr, "got buffer %lli\n", p_buffer->end_date - p_buffer->start_date);
p_aout->p_vlc->pf_memcpy( p_stream, p_buffer->p_buffer, i_len ); p_aout->p_vlc->pf_memcpy( p_stream, p_buffer->p_buffer, i_len );
aout_BufferFree( p_buffer ); aout_BufferFree( p_buffer );
} }
else else
{ {
//fprintf(stderr, "NO BUFFER !\n");
p_aout->p_vlc->pf_memset( p_stream, 0, i_len ); p_aout->p_vlc->pf_memset( p_stream, 0, i_len );
} }
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* aout.m: CoreAudio output plugin * aout.m: CoreAudio output plugin
***************************************************************************** *****************************************************************************
* Copyright (C) 2002 VideoLAN * Copyright (C) 2002 VideoLAN
* $Id: aout.m,v 1.5 2002/08/19 21:31:11 massiot Exp $ * $Id: aout.m,v 1.6 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Colin Delacroix <colin@zoy.org> * Authors: Colin Delacroix <colin@zoy.org>
* Jon Lech Johansen <jon-vl@nanocrew.net> * Jon Lech Johansen <jon-vl@nanocrew.net>
...@@ -236,7 +236,7 @@ static OSStatus IOCallback( AudioDeviceID inDevice, ...@@ -236,7 +236,7 @@ static OSStatus IOCallback( AudioDeviceID inDevice,
current_date = p_sys->clock_diff current_date = p_sys->clock_diff
+ AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000; + AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000;
p_buffer = aout_OutputNextBuffer( p_aout, current_date, 0 ); p_buffer = aout_OutputNextBuffer( p_aout, current_date, 0, VLC_FALSE );
/* move data into output data buffer */ /* move data into output data buffer */
if ( p_buffer != NULL ) if ( p_buffer != NULL )
......
...@@ -306,11 +306,11 @@ static int QNXaoutThread( aout_instance_t * p_aout ) ...@@ -306,11 +306,11 @@ static int QNXaoutThread( aout_instance_t * p_aout )
* p_aout->output.output.i_frame_length; * p_aout->output.output.i_frame_length;
next_date += mdate(); next_date += mdate();
p_buffer = aout_OutputNextBuffer( p_aout, next_date, VLC_FALSE ); p_buffer = aout_OutputNextBuffer( p_aout, next_date, 0, VLC_FALSE );
} }
else else
{ {
p_buffer = aout_OutputNextBuffer( p_aout, 0, VLC_TRUE ); p_buffer = aout_OutputNextBuffer( p_aout, 0, 0, VLC_TRUE );
} }
if ( p_buffer != NULL ) if ( p_buffer != NULL )
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* aout.c: Windows DirectX audio output method * aout.c: Windows DirectX audio output method
***************************************************************************** *****************************************************************************
* Copyright (C) 2001 VideoLAN * Copyright (C) 2001 VideoLAN
* $Id: aout.c,v 1.5 2002/08/19 21:31:11 massiot Exp $ * $Id: aout.c,v 1.6 2002/08/24 10:19:42 sam Exp $
* *
* Authors: Gildas Bazin <gbazin@netcourrier.com> * Authors: Gildas Bazin <gbazin@netcourrier.com>
* *
...@@ -559,7 +559,7 @@ static void DirectSoundThread( notification_thread_t *p_notif ) ...@@ -559,7 +559,7 @@ static void DirectSoundThread( notification_thread_t *p_notif )
} }
/* FIXME : take into account DirectSound latency instead of mdate() */ /* FIXME : take into account DirectSound latency instead of mdate() */
p_buffer = aout_OutputNextBuffer( p_aout, mdate(), 0 ); p_buffer = aout_OutputNextBuffer( p_aout, mdate(), 0, VLC_FALSE );
/* Now do the actual memcpy into the circular buffer */ /* Now do the actual memcpy into the circular buffer */
if ( l_bytes1 != p_notif->i_buffer_size ) if ( l_bytes1 != p_notif->i_buffer_size )
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* input.c : internal management of input streams for the audio output * input.c : internal management of input streams for the audio output
***************************************************************************** *****************************************************************************
* Copyright (C) 2002 VideoLAN * Copyright (C) 2002 VideoLAN
* $Id: input.c,v 1.7 2002/08/21 22:41:59 massiot Exp $ * $Id: input.c,v 1.8 2002/08/24 10:19:43 sam Exp $
* *
* Authors: Christophe Massiot <massiot@via.ecp.fr> * Authors: Christophe Massiot <massiot@via.ecp.fr>
* *
...@@ -229,7 +229,7 @@ void aout_InputPlay( aout_instance_t * p_aout, aout_input_t * p_input, ...@@ -229,7 +229,7 @@ void aout_InputPlay( aout_instance_t * p_aout, aout_input_t * p_input,
/* The decoder is _very_ late. This can only happen if the user /* The decoder is _very_ late. This can only happen if the user
* pauses the stream (or if the decoder is buggy, which cannot * pauses the stream (or if the decoder is buggy, which cannot
* happen :). */ * happen :). */
msg_Warn( p_aout, "Computed PTS is out of range (%lld), clearing out", msg_Warn( p_aout, "computed PTS is out of range (%lld), clearing out",
start_date ); start_date );
vlc_mutex_lock( &p_aout->mixer_lock ); vlc_mutex_lock( &p_aout->mixer_lock );
aout_FifoSet( p_aout, &p_input->fifo, 0 ); aout_FifoSet( p_aout, &p_input->fifo, 0 );
...@@ -237,7 +237,7 @@ void aout_InputPlay( aout_instance_t * p_aout, aout_input_t * p_input, ...@@ -237,7 +237,7 @@ void aout_InputPlay( aout_instance_t * p_aout, aout_input_t * p_input,
start_date = 0; start_date = 0;
} }
if ( p_buffer->start_date < mdate() - AOUT_MIN_PREPARE_TIME ) if ( p_buffer->start_date < mdate() + AOUT_MIN_PREPARE_TIME )
{ {
/* The decoder gives us f*cked up PTS. It's its business, but we /* The decoder gives us f*cked up PTS. It's its business, but we
* can't present it anyway, so drop the buffer. */ * can't present it anyway, so drop the buffer. */
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* output.c : internal management of output streams for the audio output * output.c : internal management of output streams for the audio output
***************************************************************************** *****************************************************************************
* Copyright (C) 2002 VideoLAN * Copyright (C) 2002 VideoLAN
* $Id: output.c,v 1.9 2002/08/21 22:41:59 massiot Exp $ * $Id: output.c,v 1.10 2002/08/24 10:19:43 sam Exp $
* *
* Authors: Christophe Massiot <massiot@via.ecp.fr> * Authors: Christophe Massiot <massiot@via.ecp.fr>
* *
...@@ -163,18 +163,31 @@ void aout_OutputPlay( aout_instance_t * p_aout, aout_buffer_t * p_buffer ) ...@@ -163,18 +163,31 @@ void aout_OutputPlay( aout_instance_t * p_aout, aout_buffer_t * p_buffer )
* compensate it by itself. S/PDIF outputs should always set b_can_sleek = 1. * compensate it by itself. S/PDIF outputs should always set b_can_sleek = 1.
*****************************************************************************/ *****************************************************************************/
aout_buffer_t * aout_OutputNextBuffer( aout_instance_t * p_aout, aout_buffer_t * aout_OutputNextBuffer( aout_instance_t * p_aout,
mtime_t start_date , mtime_t start_date,
mtime_t timeout,
vlc_bool_t b_can_sleek ) vlc_bool_t b_can_sleek )
{ {
aout_buffer_t * p_buffer; aout_buffer_t * p_buffer;
mtime_t now = mdate();
vlc_mutex_lock( &p_aout->mixer_lock ); vlc_mutex_lock( &p_aout->mixer_lock );
p_buffer = p_aout->output.fifo.p_first;
timeout += now;
while( p_aout->output.fifo.p_first == NULL && timeout > now )
{
vlc_mutex_unlock( &p_aout->mixer_lock );
msleep( AOUT_PTS_TOLERANCE / 2 );
vlc_mutex_lock( &p_aout->mixer_lock );
now = mdate();
}
p_buffer = p_aout->output.fifo.p_first;
while ( p_buffer != NULL && p_buffer->start_date < start_date ) while ( p_buffer != NULL && p_buffer->start_date < start_date )
{ {
msg_Dbg( p_aout, "audio output is too slow (%lld)", msg_Dbg( p_aout, "audio output is too slow (%lld), trashing %lldms",
start_date - p_buffer->start_date ); start_date - p_buffer->start_date,
p_buffer->end_date - p_buffer->start_date );
p_buffer = p_buffer->p_next; p_buffer = p_buffer->p_next;
} }
...@@ -185,7 +198,7 @@ aout_buffer_t * aout_OutputNextBuffer( aout_instance_t * p_aout, ...@@ -185,7 +198,7 @@ aout_buffer_t * aout_OutputNextBuffer( aout_instance_t * p_aout,
/* Set date to 0, to allow the mixer to send a new buffer ASAP */ /* Set date to 0, to allow the mixer to send a new buffer ASAP */
aout_FifoSet( p_aout, &p_aout->output.fifo, 0 ); aout_FifoSet( p_aout, &p_aout->output.fifo, 0 );
vlc_mutex_unlock( &p_aout->mixer_lock ); vlc_mutex_unlock( &p_aout->mixer_lock );
msg_Dbg( p_aout, "audio output is starving" ); msg_Dbg( p_aout, "audio output is starving, waited too long" );
return NULL; return NULL;
} }
......
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