2004-07-16 19:56:36 +00:00
/* $Id$ */
/***
This file is part of polypaudio .
polypaudio is free software ; you can redistribute it and / or modify
2004-11-14 14:58:54 +00:00
it under the terms of the GNU Lesser General Public License as published
2004-07-16 19:56:36 +00:00
by the Free Software Foundation ; either version 2 of the License ,
or ( at your option ) any later version .
polypaudio 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 .
2004-11-14 14:58:54 +00:00
You should have received a copy of the GNU Lesser General Public License
2004-07-16 19:56:36 +00:00
along with polypaudio ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307
USA .
* * */
2004-07-16 19:16:42 +00:00
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
2004-06-15 00:29:01 +00:00
# include <sys/soundcard.h>
# include <sys/ioctl.h>
2004-06-14 22:47:12 +00:00
# include <stdlib.h>
# include <sys/stat.h>
# include <stdio.h>
# include <assert.h>
# include <errno.h>
# include <string.h>
# include <fcntl.h>
# include <unistd.h>
# include <limits.h>
2006-02-16 19:19:58 +00:00
# include <polypcore/iochannel.h>
# include <polypcore/sink.h>
# include <polypcore/source.h>
# include <polypcore/module.h>
# include <polypcore/oss-util.h>
# include <polypcore/sample-util.h>
# include <polypcore/util.h>
# include <polypcore/modargs.h>
# include <polypcore/xmalloc.h>
# include <polypcore/log.h>
2004-10-30 01:55:16 +00:00
# include "module-oss-symdef.h"
2004-06-14 22:47:12 +00:00
2004-09-11 23:17:38 +00:00
PA_MODULE_AUTHOR ( " Lennart Poettering " )
PA_MODULE_DESCRIPTION ( " OSS Sink/Source " )
PA_MODULE_VERSION ( PACKAGE_VERSION )
2004-09-20 17:19:35 +00:00
PA_MODULE_USAGE ( " sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size> " )
2004-09-11 23:17:38 +00:00
2004-06-14 22:47:12 +00:00
struct userdata {
2006-01-11 01:17:39 +00:00
pa_sink * sink ;
pa_source * source ;
pa_iochannel * io ;
pa_core * core ;
2004-06-14 22:47:12 +00:00
2006-01-11 01:17:39 +00:00
pa_memchunk memchunk , silence ;
2004-06-14 22:47:12 +00:00
2004-06-18 17:12:50 +00:00
uint32_t in_fragment_size , out_fragment_size , sample_size ;
2004-09-16 00:05:56 +00:00
int use_getospace , use_getispace ;
2004-06-16 00:05:30 +00:00
int fd ;
2006-01-11 01:17:39 +00:00
pa_module * module ;
2004-06-14 22:47:12 +00:00
} ;
2004-07-11 23:21:32 +00:00
static const char * const valid_modargs [ ] = {
" sink_name " ,
" source_name " ,
" device " ,
" record " ,
" playback " ,
2004-07-16 17:51:53 +00:00
" fragments " ,
" fragment_size " ,
" format " ,
" rate " ,
" channels " ,
2004-07-11 23:21:32 +00:00
NULL
} ;
# define DEFAULT_SINK_NAME "oss_output"
# define DEFAULT_SOURCE_NAME "oss_input"
# define DEFAULT_DEVICE " / dev / dsp"
2004-08-04 16:39:30 +00:00
static void update_usage ( struct userdata * u ) {
pa_module_set_used ( u - > module ,
2006-01-11 01:17:39 +00:00
( u - > sink ? pa_idxset_size ( u - > sink - > inputs ) : 0 ) +
( u - > sink ? pa_idxset_size ( u - > sink - > monitor_source - > outputs ) : 0 ) +
( u - > source ? pa_idxset_size ( u - > source - > outputs ) : 0 ) ) ;
2004-08-04 16:39:30 +00:00
}
2004-06-14 22:47:12 +00:00
static void do_write ( struct userdata * u ) {
2006-01-11 01:17:39 +00:00
pa_memchunk * memchunk ;
2004-06-14 22:47:12 +00:00
ssize_t r ;
2004-09-16 00:05:56 +00:00
size_t l ;
int loop = 0 ;
2004-06-14 22:47:12 +00:00
assert ( u ) ;
2004-07-03 23:35:12 +00:00
if ( ! u - > sink | | ! pa_iochannel_is_writable ( u - > io ) )
2004-06-14 22:47:12 +00:00
return ;
2004-08-04 16:39:30 +00:00
update_usage ( u ) ;
2004-06-14 22:47:12 +00:00
2004-09-16 00:05:56 +00:00
l = u - > out_fragment_size ;
2004-09-01 00:23:51 +00:00
2004-09-16 00:05:56 +00:00
if ( u - > use_getospace ) {
audio_buf_info info ;
if ( ioctl ( u - > fd , SNDCTL_DSP_GETOSPACE , & info ) < 0 )
u - > use_getospace = 0 ;
else {
if ( info . bytes / l > 0 ) {
l = ( info . bytes / l ) * l ;
loop = 1 ;
}
}
2004-06-14 22:47:12 +00:00
}
2004-09-16 00:05:56 +00:00
do {
memchunk = & u - > memchunk ;
if ( ! memchunk - > length )
if ( pa_sink_render ( u - > sink , l , memchunk ) < 0 )
memchunk = & u - > silence ;
2004-09-01 00:23:51 +00:00
2004-09-16 00:05:56 +00:00
assert ( memchunk - > memblock ) ;
assert ( memchunk - > memblock - > data ) ;
assert ( memchunk - > length ) ;
2004-06-14 22:47:12 +00:00
2004-09-16 00:05:56 +00:00
if ( ( r = pa_iochannel_write ( u - > io , ( uint8_t * ) memchunk - > memblock - > data + memchunk - > index , memchunk - > length ) ) < 0 ) {
pa_log ( __FILE__ " : write() failed: %s \n " , strerror ( errno ) ) ;
break ;
2004-06-14 22:47:12 +00:00
}
2004-09-16 00:05:56 +00:00
if ( memchunk = = & u - > silence )
assert ( r % u - > sample_size = = 0 ) ;
else {
u - > memchunk . index + = r ;
u - > memchunk . length - = r ;
if ( u - > memchunk . length < = 0 ) {
pa_memblock_unref ( u - > memchunk . memblock ) ;
u - > memchunk . memblock = NULL ;
}
}
l = l > ( size_t ) r ? l - r : 0 ;
} while ( loop & & l > 0 ) ;
2004-06-14 22:47:12 +00:00
}
static void do_read ( struct userdata * u ) {
2006-01-11 01:17:39 +00:00
pa_memchunk memchunk ;
2004-06-14 22:47:12 +00:00
ssize_t r ;
2004-09-16 00:05:56 +00:00
size_t l ;
int loop = 0 ;
2004-06-14 22:47:12 +00:00
assert ( u ) ;
2006-01-12 16:11:54 +00:00
if ( ! u - > source | | ! pa_iochannel_is_readable ( u - > io ) | | ! pa_idxset_size ( u - > source - > outputs ) )
2004-06-14 22:47:12 +00:00
return ;
2004-08-04 16:39:30 +00:00
update_usage ( u ) ;
2004-09-16 00:05:56 +00:00
l = u - > in_fragment_size ;
2004-06-14 22:47:12 +00:00
2004-09-16 00:05:56 +00:00
if ( u - > use_getispace ) {
audio_buf_info info ;
if ( ioctl ( u - > fd , SNDCTL_DSP_GETISPACE , & info ) < 0 )
u - > use_getispace = 0 ;
else {
if ( info . bytes / l > 0 ) {
l = ( info . bytes / l ) * l ;
loop = 1 ;
}
}
}
do {
memchunk . memblock = pa_memblock_new ( l , u - > core - > memblock_stat ) ;
assert ( memchunk . memblock ) ;
if ( ( r = pa_iochannel_read ( u - > io , memchunk . memblock - > data , memchunk . memblock - > length ) ) < 0 ) {
pa_memblock_unref ( memchunk . memblock ) ;
if ( errno ! = EAGAIN )
pa_log ( __FILE__ " : read() failed: %s \n " , strerror ( errno ) ) ;
break ;
}
assert ( r < = ( ssize_t ) memchunk . memblock - > length ) ;
memchunk . length = memchunk . memblock - > length = r ;
memchunk . index = 0 ;
pa_source_post ( u - > source , & memchunk ) ;
pa_memblock_unref ( memchunk . memblock ) ;
2004-06-14 22:47:12 +00:00
2004-09-16 00:05:56 +00:00
l = l > ( size_t ) r ? l - r : 0 ;
} while ( loop & & l > 0 ) ;
2004-08-19 23:14:59 +00:00
}
2004-06-14 22:47:12 +00:00
2006-01-11 01:17:39 +00:00
static void io_callback ( PA_GCC_UNUSED pa_iochannel * io , void * userdata ) {
2004-06-14 22:47:12 +00:00
struct userdata * u = userdata ;
assert ( u ) ;
do_write ( u ) ;
do_read ( u ) ;
}
2006-01-12 16:11:54 +00:00
static void source_notify_cb ( pa_source * s ) {
struct userdata * u = s - > userdata ;
assert ( u ) ;
do_read ( u ) ;
}
2006-01-11 01:17:39 +00:00
static pa_usec_t sink_get_latency_cb ( pa_sink * s ) {
2004-09-13 13:26:44 +00:00
pa_usec_t r = 0 ;
2004-06-16 00:05:30 +00:00
int arg ;
struct userdata * u = s - > userdata ;
2004-06-18 17:12:50 +00:00
assert ( s & & u & & u - > sink ) ;
2004-06-16 00:05:30 +00:00
if ( ioctl ( u - > fd , SNDCTL_DSP_GETODELAY , & arg ) < 0 ) {
2006-01-27 16:25:31 +00:00
pa_log_info ( __FILE__ " : device doesn't support SNDCTL_DSP_GETODELAY: %s \n " , strerror ( errno ) ) ;
2004-06-16 00:05:30 +00:00
s - > get_latency = NULL ;
return 0 ;
}
2004-09-13 13:26:44 +00:00
r + = pa_bytes_to_usec ( arg , & s - > sample_spec ) ;
if ( u - > memchunk . memblock )
r + = pa_bytes_to_usec ( u - > memchunk . length , & s - > sample_spec ) ;
return r ;
2004-06-16 00:05:30 +00:00
}
2006-01-11 01:17:39 +00:00
static pa_usec_t source_get_latency_cb ( pa_source * s ) {
2004-09-16 00:05:56 +00:00
struct userdata * u = s - > userdata ;
audio_buf_info info ;
2004-12-11 00:10:41 +00:00
assert ( s & & u & & u - > source ) ;
2004-09-16 00:05:56 +00:00
if ( ! u - > use_getispace )
return 0 ;
if ( ioctl ( u - > fd , SNDCTL_DSP_GETISPACE , & info ) < 0 ) {
u - > use_getispace = 0 ;
return 0 ;
}
if ( info . bytes < = 0 )
return 0 ;
return pa_bytes_to_usec ( info . bytes , & s - > sample_spec ) ;
}
2006-01-27 16:25:31 +00:00
static int sink_get_hw_volume ( pa_sink * s ) {
struct userdata * u = s - > userdata ;
char cv [ PA_CVOLUME_SNPRINT_MAX ] ;
unsigned vol ;
if ( ioctl ( u - > fd , SOUND_MIXER_READ_PCM , & vol ) < 0 ) {
pa_log_info ( __FILE__ " : device doesn't support reading mixer settings: %s \n " , strerror ( errno ) ) ;
s - > get_hw_volume = NULL ;
return - 1 ;
}
s - > hw_volume . values [ 0 ] = ( ( vol & 0xFF ) * PA_VOLUME_NORM ) / 100 ;
if ( ( s - > hw_volume . channels = s - > sample_spec . channels ) > = 2 )
s - > hw_volume . values [ 1 ] = ( ( ( vol > > 8 ) & 0xFF ) * PA_VOLUME_NORM ) / 100 ;
pa_log_info ( __FILE__ " : Read mixer settings: %s \n " , pa_cvolume_snprint ( cv , sizeof ( cv ) , & s - > hw_volume ) ) ;
return 0 ;
}
static int sink_set_hw_volume ( pa_sink * s ) {
struct userdata * u = s - > userdata ;
char cv [ PA_CVOLUME_SNPRINT_MAX ] ;
unsigned vol ;
vol = ( s - > hw_volume . values [ 0 ] * 100 ) / PA_VOLUME_NORM ;
if ( s - > sample_spec . channels > = 2 )
vol | = ( ( s - > hw_volume . values [ 1 ] * 100 ) / PA_VOLUME_NORM ) < < 8 ;
if ( ioctl ( u - > fd , SOUND_MIXER_WRITE_PCM , & vol ) < 0 ) {
pa_log_info ( __FILE__ " : device doesn't support writing mixer settings: %s \n " , strerror ( errno ) ) ;
s - > set_hw_volume = NULL ;
return - 1 ;
}
pa_log_info ( __FILE__ " : Wrote mixer settings: %s \n " , pa_cvolume_snprint ( cv , sizeof ( cv ) , & s - > hw_volume ) ) ;
return 0 ;
}
2006-01-11 01:17:39 +00:00
int pa__init ( pa_core * c , pa_module * m ) {
2004-06-15 00:29:01 +00:00
struct audio_buf_info info ;
2004-06-14 22:47:12 +00:00
struct userdata * u = NULL ;
2004-07-11 23:21:32 +00:00
const char * p ;
2004-06-14 22:47:12 +00:00
int fd = - 1 ;
2004-07-16 17:51:53 +00:00
int nfrags , frag_size , in_frag_size , out_frag_size ;
2004-06-15 00:29:01 +00:00
int mode ;
2004-09-04 00:27:36 +00:00
int record = 1 , playback = 1 ;
2006-01-11 01:17:39 +00:00
pa_sample_spec ss ;
pa_modargs * ma = NULL ;
2004-06-14 22:47:12 +00:00
assert ( c & & m ) ;
2004-07-11 23:21:32 +00:00
if ( ! ( ma = pa_modargs_new ( m - > argument , valid_modargs ) ) ) {
2004-09-05 00:03:16 +00:00
pa_log ( __FILE__ " : failed to parse module arguments. \n " ) ;
2004-07-11 23:21:32 +00:00
goto fail ;
}
2004-09-04 00:27:36 +00:00
if ( pa_modargs_get_value_boolean ( ma , " record " , & record ) < 0 | | pa_modargs_get_value_boolean ( ma , " playback " , & playback ) < 0 ) {
2004-09-05 00:03:16 +00:00
pa_log ( __FILE__ " : record= and playback= expect numeric argument. \n " ) ;
2004-07-11 23:21:32 +00:00
goto fail ;
2004-06-14 22:47:12 +00:00
}
2004-09-04 00:27:36 +00:00
if ( ! playback & & ! record ) {
2004-09-05 00:03:16 +00:00
pa_log ( __FILE__ " : neither playback nor record enabled for device. \n " ) ;
2004-07-11 23:21:32 +00:00
goto fail ;
2004-06-14 22:47:12 +00:00
}
2004-06-15 15:18:33 +00:00
2004-09-04 00:27:36 +00:00
mode = ( playback & & record ) ? O_RDWR : ( playback ? O_WRONLY : ( record ? O_RDONLY : 0 ) ) ;
2004-07-16 17:51:53 +00:00
nfrags = 12 ;
frag_size = 1024 ;
2004-09-07 22:40:43 +00:00
if ( pa_modargs_get_value_s32 ( ma , " fragments " , & nfrags ) < 0 | | pa_modargs_get_value_s32 ( ma , " fragment_size " , & frag_size ) < 0 ) {
2004-09-05 00:03:16 +00:00
pa_log ( __FILE__ " : failed to parse fragments arguments \n " ) ;
2004-07-16 17:51:53 +00:00
goto fail ;
}
ss = c - > default_sample_spec ;
if ( pa_modargs_get_sample_spec ( ma , & ss ) < 0 ) {
2004-09-05 00:03:16 +00:00
pa_log ( __FILE__ " : failed to parse sample specification \n " ) ;
2004-07-16 17:51:53 +00:00
goto fail ;
}
2004-07-11 23:21:32 +00:00
if ( ( fd = pa_oss_open ( p = pa_modargs_get_value ( ma , " device " , DEFAULT_DEVICE ) , & mode , NULL ) ) < 0 )
goto fail ;
2004-12-11 00:10:41 +00:00
pa_log_info ( __FILE__ " : device opened in %s mode. \n " , mode = = O_WRONLY ? " O_WRONLY " : ( mode = = O_RDONLY ? " O_RDONLY " : " O_RDWR " ) ) ;
2004-07-16 17:51:53 +00:00
2004-09-07 22:40:43 +00:00
if ( nfrags > = 2 & & frag_size > = 1 )
if ( pa_oss_set_fragments ( fd , nfrags , frag_size ) < 0 )
goto fail ;
2004-06-14 22:47:12 +00:00
2004-07-03 23:35:12 +00:00
if ( pa_oss_auto_format ( fd , & ss ) < 0 )
2004-06-14 22:47:12 +00:00
goto fail ;
2004-06-15 00:29:01 +00:00
if ( ioctl ( fd , SNDCTL_DSP_GETBLKSIZE , & frag_size ) < 0 ) {
2004-09-05 00:03:16 +00:00
pa_log ( __FILE__ " : SNDCTL_DSP_GETBLKSIZE: %s \n " , strerror ( errno ) ) ;
2004-06-14 22:47:12 +00:00
goto fail ;
}
assert ( frag_size ) ;
2004-06-15 00:29:01 +00:00
in_frag_size = out_frag_size = frag_size ;
2004-09-16 00:05:56 +00:00
u = pa_xmalloc ( sizeof ( struct userdata ) ) ;
u - > core = c ;
u - > use_getospace = u - > use_getispace = 0 ;
2004-06-15 00:29:01 +00:00
if ( ioctl ( fd , SNDCTL_DSP_GETISPACE , & info ) > = 0 ) {
2004-12-11 00:10:41 +00:00
pa_log_info ( __FILE__ " : input -- %u fragments of size %u. \n " , info . fragstotal , info . fragsize ) ;
2004-06-15 00:29:01 +00:00
in_frag_size = info . fragsize ;
2004-09-16 00:05:56 +00:00
u - > use_getispace = 1 ;
2004-06-15 00:29:01 +00:00
}
if ( ioctl ( fd , SNDCTL_DSP_GETOSPACE , & info ) > = 0 ) {
2004-12-11 00:10:41 +00:00
pa_log_info ( __FILE__ " : output -- %u fragments of size %u. \n " , info . fragstotal , info . fragsize ) ;
2004-06-15 00:29:01 +00:00
out_frag_size = info . fragsize ;
2004-09-16 00:05:56 +00:00
u - > use_getospace = 1 ;
2004-06-15 00:29:01 +00:00
}
2004-07-10 20:56:38 +00:00
if ( mode ! = O_WRONLY ) {
2006-01-27 16:25:31 +00:00
u - > source = pa_source_new ( c , __FILE__ , pa_modargs_get_value ( ma , " source_name " , DEFAULT_SOURCE_NAME ) , 0 , & ss , NULL ) ;
2004-07-10 20:56:38 +00:00
assert ( u - > source ) ;
u - > source - > userdata = u ;
2006-01-12 16:11:54 +00:00
u - > source - > notify = source_notify_cb ;
2004-09-16 00:05:56 +00:00
u - > source - > get_latency = source_get_latency_cb ;
2004-07-10 20:56:38 +00:00
pa_source_set_owner ( u - > source , m ) ;
2004-07-14 21:52:41 +00:00
u - > source - > description = pa_sprintf_malloc ( " Open Sound System PCM on '%s' " , p ) ;
2004-07-10 20:56:38 +00:00
} else
u - > source = NULL ;
2004-06-15 00:29:01 +00:00
if ( mode ! = O_RDONLY ) {
2006-01-27 16:25:31 +00:00
u - > sink = pa_sink_new ( c , __FILE__ , pa_modargs_get_value ( ma , " sink_name " , DEFAULT_SINK_NAME ) , 0 , & ss , NULL ) ;
2004-06-14 22:47:12 +00:00
assert ( u - > sink ) ;
2004-06-16 00:05:30 +00:00
u - > sink - > get_latency = sink_get_latency_cb ;
2006-01-27 16:25:31 +00:00
u - > sink - > get_hw_volume = sink_get_hw_volume ;
u - > sink - > set_hw_volume = sink_set_hw_volume ;
2004-06-16 00:05:30 +00:00
u - > sink - > userdata = u ;
2004-07-10 20:56:38 +00:00
pa_sink_set_owner ( u - > sink , m ) ;
u - > sink - > description = pa_sprintf_malloc ( " Open Sound System PCM on '%s' " , p ) ;
2004-06-14 22:47:12 +00:00
} else
u - > sink = NULL ;
assert ( u - > source | | u - > sink ) ;
2006-01-12 16:08:14 +00:00
u - > io = pa_iochannel_new ( c - > mainloop , u - > source ? fd : - 1 , u - > sink ? fd : - 1 ) ;
2004-06-14 22:47:12 +00:00
assert ( u - > io ) ;
2004-07-03 23:35:12 +00:00
pa_iochannel_set_callback ( u - > io , io_callback , u ) ;
2004-06-16 00:05:30 +00:00
u - > fd = fd ;
2004-06-14 22:47:12 +00:00
u - > memchunk . memblock = NULL ;
u - > memchunk . length = 0 ;
2004-08-03 19:26:56 +00:00
u - > sample_size = pa_frame_size ( & ss ) ;
2004-06-14 22:47:12 +00:00
2004-06-15 00:29:01 +00:00
u - > out_fragment_size = out_frag_size ;
u - > in_fragment_size = in_frag_size ;
2004-08-17 19:37:29 +00:00
u - > silence . memblock = pa_memblock_new ( u - > silence . length = u - > out_fragment_size , u - > core - > memblock_stat ) ;
2004-06-15 00:29:01 +00:00
assert ( u - > silence . memblock ) ;
2004-07-03 23:35:12 +00:00
pa_silence_memblock ( u - > silence . memblock , & ss ) ;
2004-06-14 22:47:12 +00:00
u - > silence . index = 0 ;
2004-08-04 16:39:30 +00:00
u - > module = m ;
2004-06-14 22:47:12 +00:00
m - > userdata = u ;
2004-07-15 17:33:56 +00:00
pa_modargs_free ( ma ) ;
2006-01-12 16:09:58 +00:00
/*
* Some crappy drivers do not start the recording until we read something .
* Without this snippet , poll will never register the fd as ready .
*/
if ( u - > source ) {
char buf [ u - > sample_size ] ;
read ( u - > fd , buf , u - > sample_size ) ;
}
2006-01-27 16:25:31 +00:00
/* Read mixer settings */
if ( u - > sink )
sink_get_hw_volume ( u - > sink ) ;
2004-06-14 22:47:12 +00:00
return 0 ;
fail :
if ( fd > = 0 )
close ( fd ) ;
2004-07-11 23:21:32 +00:00
if ( ma )
pa_modargs_free ( ma ) ;
2004-06-14 22:47:12 +00:00
return - 1 ;
}
2006-01-11 01:17:39 +00:00
void pa__done ( pa_core * c , pa_module * m ) {
2004-06-14 22:47:12 +00:00
struct userdata * u ;
assert ( c & & m ) ;
2004-08-04 16:39:30 +00:00
if ( ! ( u = m - > userdata ) )
return ;
2004-06-14 22:47:12 +00:00
if ( u - > memchunk . memblock )
2004-07-03 23:35:12 +00:00
pa_memblock_unref ( u - > memchunk . memblock ) ;
2004-06-14 22:47:12 +00:00
if ( u - > silence . memblock )
2004-07-03 23:35:12 +00:00
pa_memblock_unref ( u - > silence . memblock ) ;
2004-06-15 15:18:33 +00:00
2004-09-14 20:53:25 +00:00
if ( u - > sink ) {
pa_sink_disconnect ( u - > sink ) ;
pa_sink_unref ( u - > sink ) ;
}
if ( u - > source ) {
pa_source_disconnect ( u - > source ) ;
pa_source_unref ( u - > source ) ;
}
2004-08-04 16:39:30 +00:00
2004-07-03 23:35:12 +00:00
pa_iochannel_free ( u - > io ) ;
2004-08-04 16:39:30 +00:00
pa_xfree ( u ) ;
2004-06-14 22:47:12 +00:00
}