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-07-16 17:03:11 +00:00
# include <assert.h>
# include <stdio.h>
2006-01-10 17:51:06 +00:00
# ifdef HAVE_SYS_POLL_H
2004-07-16 17:03:11 +00:00
# include <sys/poll.h>
2006-01-10 17:51:06 +00:00
# else
# include "poll.h"
# endif
2004-07-16 17:03:11 +00:00
# include <asoundlib.h>
2006-02-16 19:19:58 +00:00
# include <polypcore/core.h>
# include <polypcore/module.h>
# include <polypcore/memchunk.h>
# include <polypcore/sink.h>
# include <polypcore/modargs.h>
# include <polypcore/util.h>
# include <polypcore/sample-util.h>
# include <polypcore/xmalloc.h>
# include <polypcore/log.h>
2006-02-16 22:08:06 +00:00
# include "alsa-util.h"
2006-02-17 12:10:58 +00:00
# include "module-alsa-source-symdef.h"
2004-07-16 17:03:11 +00:00
2004-09-11 23:17:38 +00:00
PA_MODULE_AUTHOR ( " Lennart Poettering " )
PA_MODULE_DESCRIPTION ( " ALSA Source " )
PA_MODULE_VERSION ( PACKAGE_VERSION )
2004-09-20 17:19:35 +00:00
PA_MODULE_USAGE ( " source_name=<name for the source> device=<ALSA device> 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-07-16 17:03:11 +00:00
struct userdata {
snd_pcm_t * pcm_handle ;
2006-02-26 17:57:58 +00:00
snd_mixer_t * mixer_handle ;
snd_mixer_elem_t * mixer_elem ;
2006-01-11 01:17:39 +00:00
pa_source * source ;
2006-02-26 19:09:26 +00:00
struct pa_alsa_fdlist * fdl ;
2006-02-26 17:57:58 +00:00
long hw_volume_max , hw_volume_min ;
2004-07-16 17:03:11 +00:00
size_t frame_size , fragment_size ;
2006-01-11 01:17:39 +00:00
pa_memchunk memchunk ;
pa_module * module ;
2004-07-16 17:03:11 +00:00
} ;
static const char * const valid_modargs [ ] = {
" device " ,
" source_name " ,
" channels " ,
" rate " ,
2004-09-20 17:19:35 +00:00
" format " ,
2004-07-16 17:03:11 +00:00
" fragments " ,
" fragment_size " ,
NULL
} ;
# define DEFAULT_SOURCE_NAME "alsa_input"
2006-02-26 17:58:27 +00:00
# define DEFAULT_DEVICE "default"
2004-07-16 17:03:11 +00:00
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 - > source ? pa_idxset_size ( u - > source - > outputs ) : 0 ) ) ;
2004-08-04 16:39:30 +00:00
}
2004-07-16 17:03:11 +00:00
static void xrun_recovery ( struct userdata * u ) {
assert ( u ) ;
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : *** ALSA-XRUN (capture) *** " ) ;
2004-07-16 17:03:11 +00:00
if ( snd_pcm_prepare ( u - > pcm_handle ) < 0 )
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : snd_pcm_prepare() failed " ) ;
2004-07-16 17:03:11 +00:00
}
static void do_read ( struct userdata * u ) {
assert ( u ) ;
2004-08-04 16:39:30 +00:00
update_usage ( u ) ;
2004-07-16 17:03:11 +00:00
for ( ; ; ) {
2006-01-11 01:17:39 +00:00
pa_memchunk post_memchunk ;
2004-07-16 17:03:11 +00:00
snd_pcm_sframes_t frames ;
size_t l ;
if ( ! u - > memchunk . memblock ) {
2004-08-17 19:37:29 +00:00
u - > memchunk . memblock = pa_memblock_new ( u - > memchunk . length = u - > fragment_size , u - > source - > core - > memblock_stat ) ;
2004-07-16 17:03:11 +00:00
u - > memchunk . index = 0 ;
}
assert ( u - > memchunk . memblock & & u - > memchunk . memblock - > data & & u - > memchunk . length & & u - > memchunk . memblock - > length & & ( u - > memchunk . length % u - > frame_size ) = = 0 ) ;
2004-09-01 00:23:51 +00:00
if ( ( frames = snd_pcm_readi ( u - > pcm_handle , ( uint8_t * ) u - > memchunk . memblock - > data + u - > memchunk . index , u - > memchunk . length / u - > frame_size ) ) < 0 ) {
2004-07-16 17:03:11 +00:00
if ( frames = = - EAGAIN )
return ;
if ( frames = = - EPIPE ) {
xrun_recovery ( u ) ;
continue ;
}
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : snd_pcm_readi() failed: %s " , strerror ( - frames ) ) ;
2004-07-16 17:03:11 +00:00
return ;
}
l = frames * u - > frame_size ;
post_memchunk = u - > memchunk ;
post_memchunk . length = l ;
pa_source_post ( u - > source , & post_memchunk ) ;
u - > memchunk . index + = l ;
u - > memchunk . length - = l ;
if ( u - > memchunk . length = = 0 ) {
pa_memblock_unref ( u - > memchunk . memblock ) ;
u - > memchunk . memblock = NULL ;
u - > memchunk . index = u - > memchunk . length = 0 ;
}
break ;
}
}
2006-02-26 19:09:26 +00:00
static void fdl_callback ( void * userdata ) {
2004-07-16 17:03:11 +00:00
struct userdata * u = userdata ;
2006-02-26 19:09:26 +00:00
assert ( u ) ;
2004-07-16 17:03:11 +00:00
if ( snd_pcm_state ( u - > pcm_handle ) = = SND_PCM_STATE_XRUN )
xrun_recovery ( u ) ;
do_read ( u ) ;
}
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 ;
snd_pcm_sframes_t frames ;
assert ( s & & u & & u - > source ) ;
if ( snd_pcm_delay ( u - > pcm_handle , & frames ) < 0 ) {
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : failed to get delay " ) ;
2004-09-16 00:05:56 +00:00
s - > get_latency = NULL ;
return 0 ;
}
return pa_bytes_to_usec ( frames * u - > frame_size , & s - > sample_spec ) ;
}
2006-02-26 17:57:58 +00:00
static int source_get_hw_volume_cb ( pa_source * s ) {
struct userdata * u = s - > userdata ;
long vol ;
int err ;
assert ( u & & u - > mixer_elem ) ;
if ( snd_mixer_selem_has_capture_volume_joined ( u - > mixer_elem ) ) {
err = snd_mixer_selem_get_capture_volume ( u - > mixer_elem , 0 , & vol ) ;
if ( err < 0 )
goto fail ;
pa_cvolume_set ( & s - > hw_volume , s - > hw_volume . channels ,
( vol - u - > hw_volume_min ) * PA_VOLUME_NORM / ( u - > hw_volume_max - u - > hw_volume_min ) ) ;
} else {
int i ;
for ( i = 0 ; i < s - > hw_volume . channels ; i + + ) {
err = snd_mixer_selem_get_capture_volume ( u - > mixer_elem , i , & vol ) ;
if ( err < 0 )
goto fail ;
s - > hw_volume . values [ i ] =
( vol - u - > hw_volume_min ) * PA_VOLUME_NORM / ( u - > hw_volume_max - u - > hw_volume_min ) ;
}
}
return 0 ;
fail :
pa_log_error ( __FILE__ " : Unable to read volume: %s " , snd_strerror ( err ) ) ;
s - > get_hw_volume = NULL ;
s - > set_hw_volume = NULL ;
return - 1 ;
}
static int source_set_hw_volume_cb ( pa_source * s ) {
struct userdata * u = s - > userdata ;
int err ;
pa_volume_t vol ;
assert ( u & & u - > mixer_elem ) ;
if ( snd_mixer_selem_has_capture_volume_joined ( u - > mixer_elem ) ) {
vol = pa_cvolume_avg ( & s - > hw_volume ) * ( u - > hw_volume_max - u - > hw_volume_min ) /
PA_VOLUME_NORM + u - > hw_volume_min ;
err = snd_mixer_selem_set_capture_volume_all ( u - > mixer_elem , vol ) ;
if ( err < 0 )
goto fail ;
} else {
int i ;
for ( i = 0 ; i < s - > hw_volume . channels ; i + + ) {
vol = s - > hw_volume . values [ i ] * ( u - > hw_volume_max - u - > hw_volume_min ) /
PA_VOLUME_NORM + u - > hw_volume_min ;
err = snd_mixer_selem_set_capture_volume ( u - > mixer_elem , i , vol ) ;
if ( err < 0 )
goto fail ;
}
}
return 0 ;
fail :
pa_log_error ( __FILE__ " : Unable to set volume: %s " , snd_strerror ( err ) ) ;
s - > get_hw_volume = NULL ;
s - > set_hw_volume = NULL ;
return - 1 ;
}
static int source_get_hw_mute_cb ( pa_source * s ) {
struct userdata * u = s - > userdata ;
int err , sw ;
assert ( u & & u - > mixer_elem ) ;
err = snd_mixer_selem_get_capture_switch ( u - > mixer_elem , 0 , & sw ) ;
if ( err ) {
pa_log_error ( __FILE__ " : Unable to get switch: %s " , snd_strerror ( err ) ) ;
s - > get_hw_mute = NULL ;
s - > set_hw_mute = NULL ;
return - 1 ;
}
s - > hw_muted = ! sw ;
return 0 ;
}
static int source_set_hw_mute_cb ( pa_source * s ) {
struct userdata * u = s - > userdata ;
int err ;
assert ( u & & u - > mixer_elem ) ;
err = snd_mixer_selem_set_capture_switch_all ( u - > mixer_elem , ! s - > hw_muted ) ;
if ( err ) {
pa_log_error ( __FILE__ " : Unable to set switch: %s " , snd_strerror ( err ) ) ;
s - > get_hw_mute = NULL ;
s - > set_hw_mute = NULL ;
return - 1 ;
}
return 0 ;
}
2006-01-11 01:17:39 +00:00
int pa__init ( pa_core * c , pa_module * m ) {
pa_modargs * ma = NULL ;
2004-07-16 17:03:11 +00:00
int ret = - 1 ;
struct userdata * u = NULL ;
const char * dev ;
2006-01-11 01:17:39 +00:00
pa_sample_spec ss ;
2004-07-16 17:03:11 +00:00
unsigned periods , fragsize ;
2004-11-20 22:17:31 +00:00
snd_pcm_uframes_t period_size ;
2004-07-16 17:03:11 +00:00
size_t frame_size ;
2006-02-16 01:15:31 +00:00
int err ;
2004-07-16 17:03:11 +00:00
if ( ! ( ma = pa_modargs_new ( m - > argument , valid_modargs ) ) ) {
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : failed to parse module arguments " ) ;
2004-07-16 17:03:11 +00:00
goto fail ;
}
ss = c - > default_sample_spec ;
if ( pa_modargs_get_sample_spec ( ma , & ss ) < 0 ) {
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : failed to parse sample specification " ) ;
2004-07-16 17:03:11 +00:00
goto fail ;
}
2004-08-03 19:26:56 +00:00
frame_size = pa_frame_size ( & ss ) ;
2004-07-16 17:03:11 +00:00
periods = 12 ;
fragsize = 1024 ;
if ( pa_modargs_get_value_u32 ( ma , " fragments " , & periods ) < 0 | | pa_modargs_get_value_u32 ( ma , " fragment_size " , & fragsize ) < 0 ) {
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : failed to parse buffer metrics " ) ;
2004-07-16 17:03:11 +00:00
goto fail ;
}
2004-11-20 22:17:31 +00:00
period_size = fragsize ;
2004-07-16 17:03:11 +00:00
2004-08-04 16:39:30 +00:00
u = pa_xmalloc0 ( sizeof ( struct userdata ) ) ;
2004-07-16 17:03:11 +00:00
m - > userdata = u ;
2004-08-04 16:39:30 +00:00
u - > module = m ;
2004-07-16 17:03:11 +00:00
2005-09-16 00:11:48 +00:00
snd_config_update_free_global ( ) ;
2006-02-16 01:15:31 +00:00
if ( ( err = snd_pcm_open ( & u - > pcm_handle , dev = pa_modargs_get_value ( ma , " device " , DEFAULT_DEVICE ) , SND_PCM_STREAM_CAPTURE , SND_PCM_NONBLOCK ) ) < 0 ) {
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : Error opening PCM device %s: %s " , dev , snd_strerror ( err ) ) ;
2004-07-16 17:03:11 +00:00
goto fail ;
}
2006-02-16 01:15:31 +00:00
if ( ( err = pa_alsa_set_hw_params ( u - > pcm_handle , & ss , & periods , & period_size ) ) < 0 ) {
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : Failed to set hardware parameters: %s " , snd_strerror ( err ) ) ;
2004-07-16 17:03:11 +00:00
goto fail ;
}
2006-02-26 17:57:58 +00:00
if ( ( err = snd_mixer_open ( & u - > mixer_handle , 0 ) ) < 0 ) {
pa_log ( __FILE__ " : Error opening mixer: %s " , snd_strerror ( err ) ) ;
goto fail ;
}
if ( ( pa_alsa_prepare_mixer ( u - > mixer_handle , dev ) < 0 ) | |
! ( u - > mixer_elem = pa_alsa_find_elem ( u - > mixer_handle , " Capture " ) ) ) {
snd_mixer_close ( u - > mixer_handle ) ;
u - > mixer_handle = NULL ;
}
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-16 17:03:11 +00:00
assert ( u - > source ) ;
u - > source - > userdata = u ;
2004-09-16 00:05:56 +00:00
u - > source - > get_latency = source_get_latency_cb ;
2006-02-26 17:57:58 +00:00
if ( u - > mixer_handle ) {
assert ( u - > mixer_elem ) ;
if ( snd_mixer_selem_has_capture_volume ( u - > mixer_elem ) ) {
u - > source - > get_hw_volume = source_get_hw_volume_cb ;
u - > source - > set_hw_volume = source_set_hw_volume_cb ;
snd_mixer_selem_get_capture_volume_range (
u - > mixer_elem , & u - > hw_volume_min , & u - > hw_volume_max ) ;
}
if ( snd_mixer_selem_has_capture_switch ( u - > mixer_elem ) ) {
u - > source - > get_hw_mute = source_get_hw_mute_cb ;
u - > source - > set_hw_mute = source_set_hw_mute_cb ;
}
}
2004-07-16 17:03:11 +00:00
pa_source_set_owner ( u - > source , m ) ;
u - > source - > description = pa_sprintf_malloc ( " Advanced Linux Sound Architecture PCM on '%s' " , dev ) ;
2006-02-26 19:09:26 +00:00
u - > fdl = pa_alsa_fdlist_new ( ) ;
assert ( u - > fdl ) ;
if ( pa_alsa_fdlist_init_pcm ( u - > fdl , u - > pcm_handle , c - > mainloop , fdl_callback , u ) < 0 ) {
pa_log ( __FILE__ " : failed to initialise file descriptor monitoring " ) ;
2004-07-16 17:03:11 +00:00
goto fail ;
}
u - > frame_size = frame_size ;
2004-11-20 22:17:31 +00:00
u - > fragment_size = period_size ;
2004-07-16 17:03:11 +00:00
2006-02-23 02:27:19 +00:00
pa_log ( __FILE__ " : using %u fragments of size %u bytes. " , periods , u - > fragment_size ) ;
2004-07-16 17:03:11 +00:00
u - > memchunk . memblock = NULL ;
u - > memchunk . index = u - > memchunk . length = 0 ;
snd_pcm_start ( u - > pcm_handle ) ;
ret = 0 ;
finish :
if ( ma )
pa_modargs_free ( ma ) ;
2006-02-26 17:57:58 +00:00
/* Get initial mixer settings */
if ( u - > source - > get_hw_volume )
u - > source - > get_hw_volume ( u - > source ) ;
if ( u - > source - > get_hw_mute )
u - > source - > get_hw_mute ( u - > source ) ;
2004-07-16 17:03:11 +00:00
return ret ;
fail :
if ( u )
2004-09-11 23:17:38 +00:00
pa__done ( c , m ) ;
2004-07-16 17:03:11 +00:00
goto finish ;
}
2006-01-11 01:17:39 +00:00
void pa__done ( pa_core * c , pa_module * m ) {
2004-07-16 17:03:11 +00:00
struct userdata * u ;
assert ( c & & m ) ;
2004-08-04 16:39:30 +00:00
if ( ! ( u = m - > userdata ) )
return ;
2004-09-14 20:53:25 +00:00
if ( u - > source ) {
pa_source_disconnect ( u - > source ) ;
pa_source_unref ( u - > source ) ;
}
2004-08-04 16:39:30 +00:00
2006-02-26 19:09:26 +00:00
if ( u - > fdl )
pa_alsa_fdlist_free ( u - > fdl ) ;
2006-02-26 17:57:58 +00:00
if ( u - > mixer_handle )
snd_mixer_close ( u - > mixer_handle ) ;
2004-08-04 16:39:30 +00:00
if ( u - > pcm_handle ) {
snd_pcm_drop ( u - > pcm_handle ) ;
snd_pcm_close ( u - > pcm_handle ) ;
2004-07-16 17:03:11 +00:00
}
2004-08-04 16:39:30 +00:00
if ( u - > memchunk . memblock )
pa_memblock_unref ( u - > memchunk . memblock ) ;
pa_xfree ( u ) ;
2004-07-16 17:03:11 +00:00
}