2004-07-16 19:56:36 +00:00
/***
2006-06-19 21:53:48 +00:00
This file is part of PulseAudio .
2007-01-04 13:43:45 +00:00
2007-02-13 15:35:19 +00:00
Copyright 2004 - 2006 Lennart Poettering
Copyright 2006 Pierre Ossman < ossman @ cendio . se > for Cendio AB
2006-06-19 21:53:48 +00:00
PulseAudio 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 .
2007-01-04 13:43:45 +00:00
2006-06-19 21:53:48 +00:00
PulseAudio is distributed in the hope that it will be useful , but
2004-07-16 19:56:36 +00:00
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 .
2007-01-04 13:43:45 +00:00
2004-11-14 14:58:54 +00:00
You should have received a copy of the GNU Lesser General Public License
2006-06-19 21:53:48 +00:00
along with PulseAudio ; if not , write to the Free Software
2004-07-16 19:56:36 +00:00
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-11-17 23:11:34 +00:00
# include <sys/types.h>
2008-05-15 23:34:41 +00:00
# include <limits.h>
2004-07-16 17:03:11 +00:00
# include <asoundlib.h>
2006-06-19 21:53:48 +00:00
# include <pulse/sample.h>
# include <pulse/xmalloc.h>
2006-05-17 16:34:18 +00:00
2006-06-19 21:53:48 +00:00
# include <pulsecore/log.h>
2007-10-28 19:13:50 +00:00
# include <pulsecore/macro.h>
2007-11-13 17:37:44 +00:00
# include <pulsecore/core-util.h>
2008-05-15 23:34:41 +00:00
# include <pulsecore/atomic.h>
2006-02-16 22:08:06 +00:00
# include "alsa-util.h"
2004-07-16 17:03:11 +00:00
2006-02-26 19:09:26 +00:00
struct pa_alsa_fdlist {
2008-08-19 22:39:54 +02:00
unsigned num_fds ;
2006-02-26 19:09:26 +00:00
struct pollfd * fds ;
/* This is a temporary buffer used to avoid lots of mallocs */
struct pollfd * work_fds ;
2006-02-26 21:50:55 +00:00
snd_mixer_t * mixer ;
2006-02-26 19:09:26 +00:00
pa_mainloop_api * m ;
pa_defer_event * defer ;
pa_io_event * * ios ;
2008-08-19 22:39:54 +02:00
pa_bool_t polled ;
2006-02-26 19:09:26 +00:00
void ( * cb ) ( void * userdata ) ;
void * userdata ;
} ;
2008-08-09 16:20:29 +02:00
static void io_cb ( pa_mainloop_api * a , pa_io_event * e , int fd , pa_io_event_flags_t events , void * userdata ) {
2007-10-28 19:13:50 +00:00
struct pa_alsa_fdlist * fdl = userdata ;
2008-08-19 22:39:54 +02:00
int err ;
unsigned i ;
2006-02-26 19:09:26 +00:00
unsigned short revents ;
2007-10-28 19:13:50 +00:00
pa_assert ( a ) ;
pa_assert ( fdl ) ;
pa_assert ( fdl - > mixer ) ;
pa_assert ( fdl - > fds ) ;
pa_assert ( fdl - > work_fds ) ;
2006-02-26 19:09:26 +00:00
if ( fdl - > polled )
return ;
2008-08-19 22:39:54 +02:00
fdl - > polled = TRUE ;
2006-02-26 19:09:26 +00:00
memcpy ( fdl - > work_fds , fdl - > fds , sizeof ( struct pollfd ) * fdl - > num_fds ) ;
2008-08-19 22:39:54 +02:00
for ( i = 0 ; i < fdl - > num_fds ; i + + ) {
2006-02-26 19:09:26 +00:00
if ( e = = fdl - > ios [ i ] ) {
if ( events & PA_IO_EVENT_INPUT )
fdl - > work_fds [ i ] . revents | = POLLIN ;
if ( events & PA_IO_EVENT_OUTPUT )
fdl - > work_fds [ i ] . revents | = POLLOUT ;
if ( events & PA_IO_EVENT_ERROR )
fdl - > work_fds [ i ] . revents | = POLLERR ;
if ( events & PA_IO_EVENT_HANGUP )
fdl - > work_fds [ i ] . revents | = POLLHUP ;
break ;
}
}
2007-10-28 19:13:50 +00:00
pa_assert ( i ! = fdl - > num_fds ) ;
2006-02-26 21:50:55 +00:00
2007-10-28 19:13:50 +00:00
if ( ( err = snd_mixer_poll_descriptors_revents ( fdl - > mixer , fdl - > work_fds , fdl - > num_fds , & revents ) ) < 0 ) {
pa_log_error ( " Unable to get poll revent: %s " , snd_strerror ( err ) ) ;
2006-02-26 19:09:26 +00:00
return ;
}
2006-08-11 15:06:03 +00:00
a - > defer_enable ( fdl - > defer , 1 ) ;
2007-10-28 19:13:50 +00:00
if ( revents )
snd_mixer_handle_events ( fdl - > mixer ) ;
2006-02-26 19:09:26 +00:00
}
2008-08-09 16:20:29 +02:00
static void defer_cb ( pa_mainloop_api * a , pa_defer_event * e , void * userdata ) {
2007-10-28 19:13:50 +00:00
struct pa_alsa_fdlist * fdl = userdata ;
2008-08-19 22:39:54 +02:00
unsigned num_fds , i ;
int err ;
2006-02-26 19:09:26 +00:00
struct pollfd * temp ;
2007-10-28 19:13:50 +00:00
pa_assert ( a ) ;
pa_assert ( fdl ) ;
pa_assert ( fdl - > mixer ) ;
2006-02-26 19:09:26 +00:00
2006-04-16 09:23:27 +00:00
a - > defer_enable ( fdl - > defer , 0 ) ;
2008-08-19 22:39:54 +02:00
num_fds = ( unsigned ) snd_mixer_poll_descriptors_count ( fdl - > mixer ) ;
2006-02-26 19:09:26 +00:00
if ( num_fds ! = fdl - > num_fds ) {
if ( fdl - > fds )
pa_xfree ( fdl - > fds ) ;
if ( fdl - > work_fds )
pa_xfree ( fdl - > work_fds ) ;
2007-10-28 19:13:50 +00:00
fdl - > fds = pa_xnew0 ( struct pollfd , num_fds ) ;
fdl - > work_fds = pa_xnew ( struct pollfd , num_fds ) ;
2006-02-26 19:09:26 +00:00
}
memset ( fdl - > work_fds , 0 , sizeof ( struct pollfd ) * num_fds ) ;
2006-02-26 21:50:55 +00:00
2007-10-28 19:13:50 +00:00
if ( ( err = snd_mixer_poll_descriptors ( fdl - > mixer , fdl - > work_fds , num_fds ) ) < 0 ) {
pa_log_error ( " Unable to get poll descriptors: %s " , snd_strerror ( err ) ) ;
2006-02-26 19:09:26 +00:00
return ;
}
2008-08-19 22:39:54 +02:00
fdl - > polled = FALSE ;
2006-02-26 19:09:26 +00:00
if ( memcmp ( fdl - > fds , fdl - > work_fds , sizeof ( struct pollfd ) * num_fds ) = = 0 )
return ;
if ( fdl - > ios ) {
2007-10-28 19:13:50 +00:00
for ( i = 0 ; i < fdl - > num_fds ; i + + )
2006-02-26 19:09:26 +00:00
a - > io_free ( fdl - > ios [ i ] ) ;
2007-10-28 19:13:50 +00:00
2006-02-26 19:09:26 +00:00
if ( num_fds ! = fdl - > num_fds ) {
pa_xfree ( fdl - > ios ) ;
2007-10-28 19:13:50 +00:00
fdl - > ios = NULL ;
2006-02-26 19:09:26 +00:00
}
}
2007-10-28 19:13:50 +00:00
if ( ! fdl - > ios )
fdl - > ios = pa_xnew ( pa_io_event * , num_fds ) ;
2006-02-26 19:09:26 +00:00
/* Swap pointers */
temp = fdl - > work_fds ;
fdl - > work_fds = fdl - > fds ;
fdl - > fds = temp ;
fdl - > num_fds = num_fds ;
2007-10-28 19:13:50 +00:00
for ( i = 0 ; i < num_fds ; i + + )
2006-02-26 19:09:26 +00:00
fdl - > ios [ i ] = a - > io_new ( a , fdl - > fds [ i ] . fd ,
( ( fdl - > fds [ i ] . events & POLLIN ) ? PA_IO_EVENT_INPUT : 0 ) |
( ( fdl - > fds [ i ] . events & POLLOUT ) ? PA_IO_EVENT_OUTPUT : 0 ) ,
io_cb , fdl ) ;
}
struct pa_alsa_fdlist * pa_alsa_fdlist_new ( void ) {
struct pa_alsa_fdlist * fdl ;
2007-10-28 19:13:50 +00:00
fdl = pa_xnew0 ( struct pa_alsa_fdlist , 1 ) ;
2006-02-26 19:09:26 +00:00
fdl - > num_fds = 0 ;
fdl - > fds = NULL ;
fdl - > work_fds = NULL ;
2006-02-26 21:50:55 +00:00
fdl - > mixer = NULL ;
2006-02-26 19:09:26 +00:00
fdl - > m = NULL ;
fdl - > defer = NULL ;
fdl - > ios = NULL ;
2008-08-19 22:39:54 +02:00
fdl - > polled = FALSE ;
2006-02-26 19:09:26 +00:00
return fdl ;
}
void pa_alsa_fdlist_free ( struct pa_alsa_fdlist * fdl ) {
2007-10-28 19:13:50 +00:00
pa_assert ( fdl ) ;
2006-02-26 19:09:26 +00:00
if ( fdl - > defer ) {
2007-10-28 19:13:50 +00:00
pa_assert ( fdl - > m ) ;
2006-02-26 19:09:26 +00:00
fdl - > m - > defer_free ( fdl - > defer ) ;
}
if ( fdl - > ios ) {
2008-08-19 22:39:54 +02:00
unsigned i ;
2007-10-28 19:13:50 +00:00
pa_assert ( fdl - > m ) ;
2008-08-19 22:39:54 +02:00
for ( i = 0 ; i < fdl - > num_fds ; i + + )
2006-08-11 16:56:47 +00:00
fdl - > m - > io_free ( fdl - > ios [ i ] ) ;
2006-02-26 19:09:26 +00:00
pa_xfree ( fdl - > ios ) ;
}
if ( fdl - > fds )
pa_xfree ( fdl - > fds ) ;
if ( fdl - > work_fds )
pa_xfree ( fdl - > work_fds ) ;
pa_xfree ( fdl ) ;
}
2007-10-28 19:13:50 +00:00
int pa_alsa_fdlist_set_mixer ( struct pa_alsa_fdlist * fdl , snd_mixer_t * mixer_handle , pa_mainloop_api * m ) {
pa_assert ( fdl ) ;
pa_assert ( mixer_handle ) ;
pa_assert ( m ) ;
pa_assert ( ! fdl - > m ) ;
2006-02-26 21:50:55 +00:00
fdl - > mixer = mixer_handle ;
fdl - > m = m ;
fdl - > defer = m - > defer_new ( m , defer_cb , fdl ) ;
return 0 ;
}
2006-06-16 19:33:05 +00:00
static int set_format ( snd_pcm_t * pcm_handle , snd_pcm_hw_params_t * hwparams , pa_sample_format_t * f ) {
2004-07-16 17:03:11 +00:00
static const snd_pcm_format_t format_trans [ ] = {
[ PA_SAMPLE_U8 ] = SND_PCM_FORMAT_U8 ,
[ PA_SAMPLE_ALAW ] = SND_PCM_FORMAT_A_LAW ,
[ PA_SAMPLE_ULAW ] = SND_PCM_FORMAT_MU_LAW ,
[ PA_SAMPLE_S16LE ] = SND_PCM_FORMAT_S16_LE ,
[ PA_SAMPLE_S16BE ] = SND_PCM_FORMAT_S16_BE ,
[ PA_SAMPLE_FLOAT32LE ] = SND_PCM_FORMAT_FLOAT_LE ,
[ PA_SAMPLE_FLOAT32BE ] = SND_PCM_FORMAT_FLOAT_BE ,
2007-11-09 02:45:07 +00:00
[ PA_SAMPLE_S32LE ] = SND_PCM_FORMAT_S32_LE ,
[ PA_SAMPLE_S32BE ] = SND_PCM_FORMAT_S32_BE ,
2004-07-16 17:03:11 +00:00
} ;
2006-06-16 19:33:05 +00:00
static const pa_sample_format_t try_order [ ] = {
PA_SAMPLE_FLOAT32NE ,
PA_SAMPLE_FLOAT32RE ,
2007-11-09 02:45:07 +00:00
PA_SAMPLE_S32NE ,
PA_SAMPLE_S32RE ,
PA_SAMPLE_S16NE ,
PA_SAMPLE_S16RE ,
2006-06-16 19:33:05 +00:00
PA_SAMPLE_ALAW ,
2007-11-09 02:45:07 +00:00
PA_SAMPLE_ULAW ,
2006-06-16 19:33:05 +00:00
PA_SAMPLE_U8 ,
PA_SAMPLE_INVALID
} ;
int i , ret ;
2007-01-04 13:43:45 +00:00
2007-10-28 19:13:50 +00:00
pa_assert ( pcm_handle ) ;
pa_assert ( f ) ;
2006-06-16 19:33:05 +00:00
if ( ( ret = snd_pcm_hw_params_set_format ( pcm_handle , hwparams , format_trans [ * f ] ) ) > = 0 )
return ret ;
if ( * f = = PA_SAMPLE_FLOAT32BE )
* f = PA_SAMPLE_FLOAT32LE ;
else if ( * f = = PA_SAMPLE_FLOAT32LE )
* f = PA_SAMPLE_FLOAT32BE ;
else if ( * f = = PA_SAMPLE_S16BE )
* f = PA_SAMPLE_S16LE ;
else if ( * f = = PA_SAMPLE_S16LE )
* f = PA_SAMPLE_S16BE ;
2007-11-09 02:45:07 +00:00
else if ( * f = = PA_SAMPLE_S32BE )
* f = PA_SAMPLE_S32LE ;
else if ( * f = = PA_SAMPLE_S32LE )
* f = PA_SAMPLE_S32BE ;
2006-06-16 19:33:05 +00:00
else
goto try_auto ;
if ( ( ret = snd_pcm_hw_params_set_format ( pcm_handle , hwparams , format_trans [ * f ] ) ) > = 0 )
return ret ;
2007-01-04 13:43:45 +00:00
2006-06-16 19:33:05 +00:00
try_auto :
for ( i = 0 ; try_order [ i ] ! = PA_SAMPLE_INVALID ; i + + ) {
* f = try_order [ i ] ;
2007-01-04 13:43:45 +00:00
2006-06-16 19:33:05 +00:00
if ( ( ret = snd_pcm_hw_params_set_format ( pcm_handle , hwparams , format_trans [ * f ] ) ) > = 0 )
return ret ;
}
return - 1 ;
}
/* Set the hardware parameters of the given ALSA device. Returns the
* selected fragment settings in * period and * period_size */
2007-11-13 17:37:44 +00:00
int pa_alsa_set_hw_params (
snd_pcm_t * pcm_handle ,
pa_sample_spec * ss ,
uint32_t * periods ,
snd_pcm_uframes_t * period_size ,
2008-05-15 23:34:41 +00:00
snd_pcm_uframes_t tsched_size ,
2007-11-13 17:37:44 +00:00
pa_bool_t * use_mmap ,
2008-05-15 23:34:41 +00:00
pa_bool_t * use_tsched ,
2007-11-13 17:37:44 +00:00
pa_bool_t require_exact_channel_number ) {
2006-06-16 19:33:05 +00:00
int ret = - 1 ;
2008-05-15 23:34:41 +00:00
snd_pcm_uframes_t _period_size = * period_size ;
unsigned int _periods = * periods ;
2006-06-16 19:33:05 +00:00
snd_pcm_uframes_t buffer_size ;
unsigned int r = ss - > rate ;
unsigned int c = ss - > channels ;
pa_sample_format_t f = ss - > format ;
snd_pcm_hw_params_t * hwparams ;
2007-11-13 17:37:44 +00:00
pa_bool_t _use_mmap = use_mmap & & * use_mmap ;
2008-05-15 23:34:41 +00:00
pa_bool_t _use_tsched = use_tsched & & * use_tsched ;
int dir ;
2007-01-04 13:43:45 +00:00
2007-10-28 19:13:50 +00:00
pa_assert ( pcm_handle ) ;
pa_assert ( ss ) ;
pa_assert ( periods ) ;
pa_assert ( period_size ) ;
snd_pcm_hw_params_alloca ( & hwparams ) ;
2006-03-05 18:37:13 +00:00
2007-11-13 23:42:15 +00:00
if ( ( ret = snd_pcm_hw_params_any ( pcm_handle , hwparams ) ) < 0 )
goto finish ;
if ( ( ret = snd_pcm_hw_params_set_rate_resample ( pcm_handle , hwparams , 0 ) ) < 0 )
2007-10-28 19:13:50 +00:00
goto finish ;
2007-11-13 17:37:44 +00:00
if ( _use_mmap ) {
2007-10-28 19:13:50 +00:00
if ( ( ret = snd_pcm_hw_params_set_access ( pcm_handle , hwparams , SND_PCM_ACCESS_MMAP_INTERLEAVED ) ) < 0 ) {
/* mmap() didn't work, fall back to interleaved */
if ( ( ret = snd_pcm_hw_params_set_access ( pcm_handle , hwparams , SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
goto finish ;
2007-11-13 17:37:44 +00:00
_use_mmap = FALSE ;
2007-10-28 19:13:50 +00:00
}
} else if ( ( ret = snd_pcm_hw_params_set_access ( pcm_handle , hwparams , SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
2006-06-17 23:36:03 +00:00
goto finish ;
2008-05-15 23:34:41 +00:00
if ( ! _use_mmap )
_use_tsched = FALSE ;
2006-06-17 23:36:03 +00:00
if ( ( ret = set_format ( pcm_handle , hwparams , & f ) ) < 0 )
goto finish ;
if ( ( ret = snd_pcm_hw_params_set_rate_near ( pcm_handle , hwparams , & r , NULL ) ) < 0 )
goto finish ;
2008-05-15 23:34:41 +00:00
/* Adjust the buffer sizes, if we didn't get the rate we were asking for */
_period_size = ( snd_pcm_uframes_t ) ( ( ( uint64_t ) _period_size * r ) / ss - > rate ) ;
tsched_size = ( snd_pcm_uframes_t ) ( ( ( uint64_t ) tsched_size * r ) / ss - > rate ) ;
2007-11-13 17:37:44 +00:00
if ( require_exact_channel_number ) {
if ( ( ret = snd_pcm_hw_params_set_channels ( pcm_handle , hwparams , c ) ) < 0 )
goto finish ;
} else {
if ( ( ret = snd_pcm_hw_params_set_channels_near ( pcm_handle , hwparams , & c ) ) < 0 )
goto finish ;
}
2006-06-17 23:36:03 +00:00
2008-05-15 23:34:41 +00:00
if ( _use_tsched ) {
_period_size = tsched_size ;
_periods = 1 ;
pa_assert_se ( snd_pcm_hw_params_get_buffer_size_max ( hwparams , & buffer_size ) = = 0 ) ;
pa_log_debug ( " Maximum hw buffer size is %u ms " , ( unsigned ) buffer_size * 1000 / r ) ;
}
buffer_size = _periods * _period_size ;
if ( ( ret = snd_pcm_hw_params_set_periods_integer ( pcm_handle , hwparams ) ) < 0 )
2006-06-17 23:36:03 +00:00
goto finish ;
2008-05-15 23:34:41 +00:00
if ( _periods > 0 ) {
dir = 1 ;
if ( ( ret = snd_pcm_hw_params_set_periods_near ( pcm_handle , hwparams , & _periods , & dir ) ) < 0 ) {
dir = - 1 ;
if ( ( ret = snd_pcm_hw_params_set_periods_near ( pcm_handle , hwparams , & _periods , & dir ) ) < 0 )
goto finish ;
}
}
if ( _period_size > 0 )
if ( ( ret = snd_pcm_hw_params_set_buffer_size_near ( pcm_handle , hwparams , & buffer_size ) ) < 0 )
goto finish ;
2006-06-17 23:36:03 +00:00
if ( ( ret = snd_pcm_hw_params ( pcm_handle , hwparams ) ) < 0 )
2004-11-20 22:17:31 +00:00
goto finish ;
2006-02-20 23:47:46 +00:00
2007-11-13 17:37:44 +00:00
if ( ss - > rate ! = r )
2007-10-28 19:13:50 +00:00
pa_log_warn ( " Device %s doesn't support %u Hz, changed to %u Hz. " , snd_pcm_name ( pcm_handle ) , ss - > rate , r ) ;
2006-05-30 22:05:07 +00:00
2007-11-13 17:37:44 +00:00
if ( ss - > channels ! = c )
2007-10-28 19:13:50 +00:00
pa_log_warn ( " Device %s doesn't support %u channels, changed to %u. " , snd_pcm_name ( pcm_handle ) , ss - > channels , c ) ;
2006-06-16 19:33:05 +00:00
2007-11-13 17:37:44 +00:00
if ( ss - > format ! = f )
2007-10-28 19:13:50 +00:00
pa_log_warn ( " Device %s doesn't support sample format %s, changed to %s. " , snd_pcm_name ( pcm_handle ) , pa_sample_format_to_string ( ss - > format ) , pa_sample_format_to_string ( f ) ) ;
2007-01-04 13:43:45 +00:00
2006-03-05 18:35:45 +00:00
if ( ( ret = snd_pcm_prepare ( pcm_handle ) ) < 0 )
2004-11-20 22:17:31 +00:00
goto finish ;
2008-05-15 23:34:41 +00:00
if ( ( ret = snd_pcm_hw_params_get_period_size ( hwparams , & _period_size , & dir ) ) < 0 | |
( ret = snd_pcm_hw_params_get_periods ( hwparams , & _periods , & dir ) ) < 0 )
2004-11-20 22:17:31 +00:00
goto finish ;
2007-01-04 13:43:45 +00:00
2007-11-13 17:37:44 +00:00
/* If the sample rate deviates too much, we need to resample */
if ( r < ss - > rate * .95 | | r > ss - > rate * 1.05 )
ss - > rate = r ;
2008-08-19 22:39:54 +02:00
ss - > channels = ( uint8_t ) c ;
2007-11-13 17:37:44 +00:00
ss - > format = f ;
2008-05-15 23:34:41 +00:00
pa_assert ( _periods > 0 ) ;
pa_assert ( _period_size > 0 ) ;
* periods = _periods ;
* period_size = _period_size ;
2007-01-04 13:43:45 +00:00
2007-11-13 17:37:44 +00:00
if ( use_mmap )
* use_mmap = _use_mmap ;
2008-05-15 23:34:41 +00:00
if ( use_tsched )
* use_tsched = _use_tsched ;
2004-11-20 22:17:31 +00:00
ret = 0 ;
2007-01-04 13:43:45 +00:00
2004-11-20 22:17:31 +00:00
finish :
2007-01-04 13:43:45 +00:00
2004-07-16 17:03:11 +00:00
return ret ;
}
2008-05-15 23:34:41 +00:00
int pa_alsa_set_sw_params ( snd_pcm_t * pcm , snd_pcm_uframes_t avail_min ) {
2007-10-28 19:13:50 +00:00
snd_pcm_sw_params_t * swparams ;
int err ;
pa_assert ( pcm ) ;
snd_pcm_sw_params_alloca ( & swparams ) ;
if ( ( err = snd_pcm_sw_params_current ( pcm , swparams ) < 0 ) ) {
pa_log_warn ( " Unable to determine current swparams: %s \n " , snd_strerror ( err ) ) ;
return err ;
}
if ( ( err = snd_pcm_sw_params_set_stop_threshold ( pcm , swparams , ( snd_pcm_uframes_t ) - 1 ) ) < 0 ) {
pa_log_warn ( " Unable to set stop threshold: %s \n " , snd_strerror ( err ) ) ;
return err ;
}
if ( ( err = snd_pcm_sw_params_set_start_threshold ( pcm , swparams , ( snd_pcm_uframes_t ) - 1 ) ) < 0 ) {
pa_log_warn ( " Unable to set start threshold: %s \n " , snd_strerror ( err ) ) ;
return err ;
}
2008-05-15 23:34:41 +00:00
if ( ( err = snd_pcm_sw_params_set_avail_min ( pcm , swparams , avail_min ) ) < 0 ) {
pa_log_error ( " snd_pcm_sw_params_set_avail_min() failed: %s " , snd_strerror ( err ) ) ;
return err ;
}
2007-10-28 19:13:50 +00:00
if ( ( err = snd_pcm_sw_params ( pcm , swparams ) ) < 0 ) {
pa_log_warn ( " Unable to set sw params: %s \n " , snd_strerror ( err ) ) ;
return err ;
}
return 0 ;
}
2007-11-13 17:37:44 +00:00
struct device_info {
pa_channel_map map ;
const char * name ;
} ;
static const struct device_info device_table [ ] = {
{ { 2 , { PA_CHANNEL_POSITION_LEFT , PA_CHANNEL_POSITION_RIGHT } } , " front " } ,
{ { 4 , { PA_CHANNEL_POSITION_FRONT_LEFT , PA_CHANNEL_POSITION_FRONT_RIGHT ,
PA_CHANNEL_POSITION_REAR_LEFT , PA_CHANNEL_POSITION_REAR_RIGHT } } , " surround40 " } ,
{ { 5 , { PA_CHANNEL_POSITION_FRONT_LEFT , PA_CHANNEL_POSITION_FRONT_RIGHT ,
PA_CHANNEL_POSITION_REAR_LEFT , PA_CHANNEL_POSITION_REAR_RIGHT ,
PA_CHANNEL_POSITION_LFE } } , " surround41 " } ,
{ { 5 , { PA_CHANNEL_POSITION_FRONT_LEFT , PA_CHANNEL_POSITION_FRONT_RIGHT ,
PA_CHANNEL_POSITION_REAR_LEFT , PA_CHANNEL_POSITION_REAR_RIGHT ,
PA_CHANNEL_POSITION_CENTER } } , " surround50 " } ,
{ { 6 , { PA_CHANNEL_POSITION_FRONT_LEFT , PA_CHANNEL_POSITION_FRONT_RIGHT ,
PA_CHANNEL_POSITION_REAR_LEFT , PA_CHANNEL_POSITION_REAR_RIGHT ,
PA_CHANNEL_POSITION_CENTER , PA_CHANNEL_POSITION_LFE } } , " surround51 " } ,
{ { 8 , { PA_CHANNEL_POSITION_FRONT_LEFT , PA_CHANNEL_POSITION_FRONT_RIGHT ,
PA_CHANNEL_POSITION_REAR_LEFT , PA_CHANNEL_POSITION_REAR_RIGHT ,
PA_CHANNEL_POSITION_CENTER , PA_CHANNEL_POSITION_LFE ,
PA_CHANNEL_POSITION_SIDE_LEFT , PA_CHANNEL_POSITION_SIDE_RIGHT } } , " surround71 " } ,
{ { 0 , { 0 } } , NULL }
} ;
static pa_bool_t channel_map_superset ( const pa_channel_map * a , const pa_channel_map * b ) {
pa_bool_t in_a [ PA_CHANNEL_POSITION_MAX ] ;
unsigned i ;
pa_assert ( a ) ;
pa_assert ( b ) ;
memset ( in_a , 0 , sizeof ( in_a ) ) ;
for ( i = 0 ; i < a - > channels ; i + + )
in_a [ a - > map [ i ] ] = TRUE ;
for ( i = 0 ; i < b - > channels ; i + + )
if ( ! in_a [ b - > map [ i ] ] )
return FALSE ;
return TRUE ;
}
snd_pcm_t * pa_alsa_open_by_device_id (
const char * dev_id ,
char * * dev ,
pa_sample_spec * ss ,
pa_channel_map * map ,
int mode ,
uint32_t * nfrags ,
snd_pcm_uframes_t * period_size ,
2008-05-15 23:34:41 +00:00
snd_pcm_uframes_t tsched_size ,
pa_bool_t * use_mmap ,
pa_bool_t * use_tsched ) {
2007-11-13 17:37:44 +00:00
int i ;
int direction = 1 ;
int err ;
char * d ;
snd_pcm_t * pcm_handle ;
pa_assert ( dev_id ) ;
pa_assert ( dev ) ;
pa_assert ( ss ) ;
pa_assert ( map ) ;
pa_assert ( nfrags ) ;
pa_assert ( period_size ) ;
/* First we try to find a device string with a superset of the
* requested channel map and open it without the plug : prefix . We
* iterate through our device table from top to bottom and take
* the first that matches . If we didn ' t find a working device that
* way , we iterate backwards , and check all devices that do not
* provide a superset of the requested channel map . */
for ( i = 0 ; ; i + = direction ) {
pa_sample_spec try_ss ;
if ( i < 0 ) {
pa_assert ( direction = = - 1 ) ;
/* OK, so we iterated backwards, and now are at the
* beginning of our list . */
break ;
} else if ( ! device_table [ i ] . name ) {
pa_assert ( direction = = 1 ) ;
/* OK, so we are at the end of our list. at iterated
* forwards . */
i - - ;
direction = - 1 ;
}
if ( ( direction > 0 ) = = ! channel_map_superset ( & device_table [ i ] . map , map ) )
continue ;
d = pa_sprintf_malloc ( " %s:%s " , device_table [ i ] . name , dev_id ) ;
pa_log_debug ( " Trying %s... " , d ) ;
2008-05-15 23:34:41 +00:00
if ( ( err = snd_pcm_open ( & pcm_handle , d , mode ,
SND_PCM_NONBLOCK |
SND_PCM_NO_AUTO_RESAMPLE |
SND_PCM_NO_AUTO_CHANNELS |
2008-07-16 11:12:07 +02:00
SND_PCM_NO_AUTO_FORMAT |
SND_PCM_NO_SOFTVOL ) ) < 0 ) {
2007-11-13 17:37:44 +00:00
pa_log_info ( " Couldn't open PCM device %s: %s " , d , snd_strerror ( err ) ) ;
pa_xfree ( d ) ;
continue ;
}
try_ss . channels = device_table [ i ] . map . channels ;
try_ss . rate = ss - > rate ;
try_ss . format = ss - > format ;
2008-05-15 23:34:41 +00:00
if ( ( err = pa_alsa_set_hw_params ( pcm_handle , & try_ss , nfrags , period_size , tsched_size , use_mmap , use_tsched , TRUE ) ) < 0 ) {
2007-11-13 17:37:44 +00:00
pa_log_info ( " PCM device %s refused our hw parameters: %s " , d , snd_strerror ( err ) ) ;
pa_xfree ( d ) ;
snd_pcm_close ( pcm_handle ) ;
continue ;
}
* ss = try_ss ;
* map = device_table [ i ] . map ;
pa_assert ( map - > channels = = ss - > channels ) ;
* dev = d ;
return pcm_handle ;
}
2008-05-15 23:34:41 +00:00
/* OK, we didn't find any good device, so let's try the raw plughw: stuff */
2007-11-13 17:37:44 +00:00
2008-05-15 23:34:41 +00:00
d = pa_sprintf_malloc ( " plughw:%s " , dev_id ) ;
2007-11-13 17:37:44 +00:00
pa_log_debug ( " Trying %s as last resort... " , d ) ;
2008-05-15 23:34:41 +00:00
pcm_handle = pa_alsa_open_by_device_string ( d , dev , ss , map , mode , nfrags , period_size , tsched_size , use_mmap , use_tsched ) ;
2007-11-13 17:37:44 +00:00
pa_xfree ( d ) ;
return pcm_handle ;
}
snd_pcm_t * pa_alsa_open_by_device_string (
const char * device ,
char * * dev ,
pa_sample_spec * ss ,
pa_channel_map * map ,
int mode ,
uint32_t * nfrags ,
snd_pcm_uframes_t * period_size ,
2008-05-15 23:34:41 +00:00
snd_pcm_uframes_t tsched_size ,
pa_bool_t * use_mmap ,
pa_bool_t * use_tsched ) {
2007-11-13 17:37:44 +00:00
int err ;
char * d ;
snd_pcm_t * pcm_handle ;
pa_assert ( device ) ;
pa_assert ( dev ) ;
pa_assert ( ss ) ;
pa_assert ( map ) ;
pa_assert ( nfrags ) ;
pa_assert ( period_size ) ;
d = pa_xstrdup ( device ) ;
for ( ; ; ) {
2008-05-15 23:34:41 +00:00
if ( ( err = snd_pcm_open ( & pcm_handle , d , mode , SND_PCM_NONBLOCK |
SND_PCM_NO_AUTO_RESAMPLE |
SND_PCM_NO_AUTO_CHANNELS |
SND_PCM_NO_AUTO_FORMAT ) ) < 0 ) {
2007-11-13 17:37:44 +00:00
pa_log ( " Error opening PCM device %s: %s " , d , snd_strerror ( err ) ) ;
pa_xfree ( d ) ;
return NULL ;
}
2008-05-15 23:34:41 +00:00
if ( ( err = pa_alsa_set_hw_params ( pcm_handle , ss , nfrags , period_size , tsched_size , use_mmap , use_tsched , FALSE ) ) < 0 ) {
2007-11-13 17:37:44 +00:00
if ( err = = - EPERM ) {
/* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */
if ( pa_startswith ( d , " hw: " ) ) {
char * t = pa_sprintf_malloc ( " plughw:%s " , d + 3 ) ;
pa_log_debug ( " Opening the device as '%s' didn't work, retrying with '%s'. " , d , t ) ;
pa_xfree ( d ) ;
d = t ;
snd_pcm_close ( pcm_handle ) ;
continue ;
}
pa_log ( " Failed to set hardware parameters on %s: %s " , d , snd_strerror ( err ) ) ;
pa_xfree ( d ) ;
snd_pcm_close ( pcm_handle ) ;
return NULL ;
}
}
* dev = d ;
2008-06-18 23:44:26 +02:00
if ( ss - > channels ! = map - > channels )
pa_channel_map_init_extend ( map , ss - > channels , PA_CHANNEL_MAP_ALSA ) ;
2007-11-13 17:37:44 +00:00
return pcm_handle ;
}
}
2006-02-26 17:57:58 +00:00
int pa_alsa_prepare_mixer ( snd_mixer_t * mixer , const char * dev ) {
int err ;
2007-10-28 19:13:50 +00:00
pa_assert ( mixer ) ;
pa_assert ( dev ) ;
2006-02-26 17:57:58 +00:00
if ( ( err = snd_mixer_attach ( mixer , dev ) ) < 0 ) {
2007-11-13 23:42:15 +00:00
pa_log_info ( " Unable to attach to mixer %s: %s " , dev , snd_strerror ( err ) ) ;
2006-02-26 17:57:58 +00:00
return - 1 ;
}
if ( ( err = snd_mixer_selem_register ( mixer , NULL , NULL ) ) < 0 ) {
2006-08-18 21:38:40 +00:00
pa_log_warn ( " Unable to register mixer: %s " , snd_strerror ( err ) ) ;
2006-02-26 17:57:58 +00:00
return - 1 ;
}
if ( ( err = snd_mixer_load ( mixer ) ) < 0 ) {
2006-08-18 21:38:40 +00:00
pa_log_warn ( " Unable to load mixer: %s " , snd_strerror ( err ) ) ;
2006-02-26 17:57:58 +00:00
return - 1 ;
}
2007-11-13 23:42:15 +00:00
pa_log_info ( " Successfully attached to mixer '%s' " , dev ) ;
2006-02-26 17:57:58 +00:00
return 0 ;
}
2006-05-30 22:48:17 +00:00
snd_mixer_elem_t * pa_alsa_find_elem ( snd_mixer_t * mixer , const char * name , const char * fallback ) {
2006-02-26 17:57:58 +00:00
snd_mixer_elem_t * elem ;
2006-05-30 22:48:17 +00:00
snd_mixer_selem_id_t * sid = NULL ;
2007-10-28 19:13:50 +00:00
2006-02-26 17:57:58 +00:00
snd_mixer_selem_id_alloca ( & sid ) ;
2007-10-28 19:13:50 +00:00
pa_assert ( mixer ) ;
pa_assert ( name ) ;
2006-02-26 17:57:58 +00:00
snd_mixer_selem_id_set_name ( sid , name ) ;
2006-05-30 22:48:17 +00:00
if ( ! ( elem = snd_mixer_find_selem ( mixer , sid ) ) ) {
2007-11-13 23:42:15 +00:00
pa_log_info ( " Cannot find mixer control \" %s \" . " , snd_mixer_selem_id_get_name ( sid ) ) ;
2006-05-30 22:48:17 +00:00
if ( fallback ) {
snd_mixer_selem_id_set_name ( sid , fallback ) ;
2007-01-04 13:43:45 +00:00
2006-05-30 22:48:17 +00:00
if ( ! ( elem = snd_mixer_find_selem ( mixer , sid ) ) )
2006-08-18 21:38:40 +00:00
pa_log_warn ( " Cannot find fallback mixer control \" %s \" . " , snd_mixer_selem_id_get_name ( sid ) ) ;
2006-05-30 22:48:17 +00:00
}
}
if ( elem )
2006-08-18 21:38:40 +00:00
pa_log_info ( " Using mixer control \" %s \" . " , snd_mixer_selem_id_get_name ( sid ) ) ;
2006-02-26 17:57:58 +00:00
return elem ;
}
2007-11-13 23:42:15 +00:00
static const snd_mixer_selem_channel_id_t alsa_channel_ids [ PA_CHANNEL_POSITION_MAX ] = {
[ PA_CHANNEL_POSITION_MONO ] = SND_MIXER_SCHN_MONO , /* The ALSA name is just an alias! */
[ PA_CHANNEL_POSITION_FRONT_CENTER ] = SND_MIXER_SCHN_FRONT_CENTER ,
[ PA_CHANNEL_POSITION_FRONT_LEFT ] = SND_MIXER_SCHN_FRONT_LEFT ,
[ PA_CHANNEL_POSITION_FRONT_RIGHT ] = SND_MIXER_SCHN_FRONT_RIGHT ,
[ PA_CHANNEL_POSITION_REAR_CENTER ] = SND_MIXER_SCHN_REAR_CENTER ,
[ PA_CHANNEL_POSITION_REAR_LEFT ] = SND_MIXER_SCHN_REAR_LEFT ,
[ PA_CHANNEL_POSITION_REAR_RIGHT ] = SND_MIXER_SCHN_REAR_RIGHT ,
[ PA_CHANNEL_POSITION_LFE ] = SND_MIXER_SCHN_WOOFER ,
[ PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_SIDE_LEFT ] = SND_MIXER_SCHN_SIDE_LEFT ,
[ PA_CHANNEL_POSITION_SIDE_RIGHT ] = SND_MIXER_SCHN_SIDE_RIGHT ,
[ PA_CHANNEL_POSITION_AUX0 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX1 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX2 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX3 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX4 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX5 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX6 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX7 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX8 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX9 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX10 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX11 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX12 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX13 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX14 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX15 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX16 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX17 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX18 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX19 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX20 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX21 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX22 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX23 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX24 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX25 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX26 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX27 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX28 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX29 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX30 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_AUX31 ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_CENTER ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_FRONT_CENTER ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_FRONT_LEFT ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_REAR_CENTER ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_REAR_LEFT ] = SND_MIXER_SCHN_UNKNOWN ,
[ PA_CHANNEL_POSITION_TOP_REAR_RIGHT ] = SND_MIXER_SCHN_UNKNOWN
} ;
int pa_alsa_calc_mixer_map ( snd_mixer_elem_t * elem , const pa_channel_map * channel_map , snd_mixer_selem_channel_id_t mixer_map [ ] , pa_bool_t playback ) {
unsigned i ;
pa_bool_t alsa_channel_used [ SND_MIXER_SCHN_LAST ] ;
pa_bool_t mono_used = FALSE ;
pa_assert ( elem ) ;
pa_assert ( channel_map ) ;
pa_assert ( mixer_map ) ;
memset ( & alsa_channel_used , 0 , sizeof ( alsa_channel_used ) ) ;
if ( channel_map - > channels > 1 & &
( ( playback & & snd_mixer_selem_has_playback_volume_joined ( elem ) ) | |
( ! playback & & snd_mixer_selem_has_capture_volume_joined ( elem ) ) ) ) {
2008-08-13 13:53:31 +02:00
pa_log_info ( " ALSA device lacks independant volume controls for each channel. " ) ;
2007-11-13 23:42:15 +00:00
return - 1 ;
}
for ( i = 0 ; i < channel_map - > channels ; i + + ) {
snd_mixer_selem_channel_id_t id ;
pa_bool_t is_mono ;
is_mono = channel_map - > map [ i ] = = PA_CHANNEL_POSITION_MONO ;
id = alsa_channel_ids [ channel_map - > map [ i ] ] ;
if ( ! is_mono & & id = = SND_MIXER_SCHN_UNKNOWN ) {
2008-08-13 13:53:31 +02:00
pa_log_info ( " Configured channel map contains channel '%s' that is unknown to the ALSA mixer. " , pa_channel_position_to_string ( channel_map - > map [ i ] ) ) ;
2007-11-13 23:42:15 +00:00
return - 1 ;
}
if ( ( is_mono & & mono_used ) | | ( ! is_mono & & alsa_channel_used [ id ] ) ) {
2008-05-15 23:34:41 +00:00
pa_log_info ( " Channel map has duplicate channel '%s', falling back to software volume control. " , pa_channel_position_to_string ( channel_map - > map [ i ] ) ) ;
2007-11-13 23:42:15 +00:00
return - 1 ;
}
if ( ( playback & & ( ! snd_mixer_selem_has_playback_channel ( elem , id ) | | ( is_mono & & ! snd_mixer_selem_is_playback_mono ( elem ) ) ) ) | |
( ! playback & & ( ! snd_mixer_selem_has_capture_channel ( elem , id ) | | ( is_mono & & ! snd_mixer_selem_is_capture_mono ( elem ) ) ) ) ) {
2008-08-13 13:53:31 +02:00
pa_log_info ( " ALSA device lacks separate volumes control for channel '%s' " , pa_channel_position_to_string ( channel_map - > map [ i ] ) ) ;
2007-11-13 23:42:15 +00:00
return - 1 ;
}
if ( is_mono ) {
mixer_map [ i ] = SND_MIXER_SCHN_MONO ;
mono_used = TRUE ;
} else {
mixer_map [ i ] = id ;
alsa_channel_used [ id ] = TRUE ;
}
}
2008-05-15 23:34:41 +00:00
pa_log_info ( " All %u channels can be mapped to mixer channels. " , channel_map - > channels ) ;
2007-11-13 23:42:15 +00:00
return 0 ;
}
2008-05-15 23:34:41 +00:00
void pa_alsa_dump ( snd_pcm_t * pcm ) {
int err ;
snd_output_t * out ;
pa_assert ( pcm ) ;
pa_assert_se ( snd_output_buffer_open ( & out ) = = 0 ) ;
if ( ( err = snd_pcm_dump ( pcm , out ) ) < 0 )
pa_log_debug ( " snd_pcm_dump(): %s " , snd_strerror ( err ) ) ;
else {
char * s = NULL ;
snd_output_buffer_string ( out , & s ) ;
pa_log_debug ( " snd_pcm_dump(): \n %s " , pa_strnull ( s ) ) ;
}
pa_assert_se ( snd_output_close ( out ) = = 0 ) ;
}
void pa_alsa_dump_status ( snd_pcm_t * pcm ) {
int err ;
snd_output_t * out ;
snd_pcm_status_t * status ;
pa_assert ( pcm ) ;
snd_pcm_status_alloca ( & status ) ;
pa_assert_se ( snd_output_buffer_open ( & out ) = = 0 ) ;
pa_assert_se ( snd_pcm_status ( pcm , status ) = = 0 ) ;
if ( ( err = snd_pcm_status_dump ( status , out ) ) < 0 )
pa_log_debug ( " snd_pcm_dump(): %s " , snd_strerror ( err ) ) ;
else {
char * s = NULL ;
snd_output_buffer_string ( out , & s ) ;
pa_log_debug ( " snd_pcm_dump(): \n %s " , pa_strnull ( s ) ) ;
}
pa_assert_se ( snd_output_close ( out ) = = 0 ) ;
}
static void alsa_error_handler ( const char * file , int line , const char * function , int err , const char * fmt , . . . ) {
va_list ap ;
va_start ( ap , fmt ) ;
pa_log_levelv_meta ( PA_LOG_WARN , file , line , function , fmt , ap ) ;
va_end ( ap ) ;
}
static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT ( 0 ) ;
void pa_alsa_redirect_errors_inc ( void ) {
/* This is not really thread safe, but we do our best */
if ( pa_atomic_inc ( & n_error_handler_installed ) = = 0 )
snd_lib_error_set_handler ( alsa_error_handler ) ;
}
void pa_alsa_redirect_errors_dec ( void ) {
int r ;
pa_assert_se ( ( r = pa_atomic_dec ( & n_error_handler_installed ) ) > = 1 ) ;
if ( r = = 1 )
snd_lib_error_set_handler ( NULL ) ;
}
void pa_alsa_init_proplist ( pa_proplist * p , snd_pcm_info_t * pcm_info ) {
static const char * const alsa_class_table [ SND_PCM_CLASS_LAST + 1 ] = {
[ SND_PCM_CLASS_GENERIC ] = " generic " ,
[ SND_PCM_CLASS_MULTI ] = " multi " ,
[ SND_PCM_CLASS_MODEM ] = " modem " ,
[ SND_PCM_CLASS_DIGITIZER ] = " digitizer "
} ;
static const char * const class_table [ SND_PCM_CLASS_LAST + 1 ] = {
[ SND_PCM_CLASS_GENERIC ] = " sound " ,
[ SND_PCM_CLASS_MULTI ] = NULL ,
[ SND_PCM_CLASS_MODEM ] = " modem " ,
[ SND_PCM_CLASS_DIGITIZER ] = NULL
} ;
static const char * const alsa_subclass_table [ SND_PCM_SUBCLASS_LAST + 1 ] = {
[ SND_PCM_SUBCLASS_GENERIC_MIX ] = " generic-mix " ,
[ SND_PCM_SUBCLASS_MULTI_MIX ] = " multi-mix "
} ;
snd_pcm_class_t class ;
snd_pcm_subclass_t subclass ;
const char * n , * id , * sdn ;
char * cn = NULL , * lcn = NULL ;
int card ;
pa_assert ( p ) ;
pa_assert ( pcm_info ) ;
pa_proplist_sets ( p , PA_PROP_DEVICE_API , " alsa " ) ;
class = snd_pcm_info_get_class ( pcm_info ) ;
if ( class < = SND_PCM_CLASS_LAST ) {
if ( class_table [ class ] )
pa_proplist_sets ( p , PA_PROP_DEVICE_CLASS , class_table [ class ] ) ;
if ( alsa_class_table [ class ] )
pa_proplist_sets ( p , " alsa.class " , alsa_class_table [ class ] ) ;
}
subclass = snd_pcm_info_get_subclass ( pcm_info ) ;
if ( subclass < = SND_PCM_SUBCLASS_LAST )
if ( alsa_subclass_table [ subclass ] )
pa_proplist_sets ( p , " alsa.subclass " , alsa_subclass_table [ subclass ] ) ;
if ( ( n = snd_pcm_info_get_name ( pcm_info ) ) )
pa_proplist_sets ( p , " alsa.name " , n ) ;
if ( ( id = snd_pcm_info_get_id ( pcm_info ) ) )
pa_proplist_sets ( p , " alsa.id " , id ) ;
pa_proplist_setf ( p , " alsa.subdevice " , " %u " , snd_pcm_info_get_subdevice ( pcm_info ) ) ;
if ( ( sdn = snd_pcm_info_get_subdevice_name ( pcm_info ) ) )
pa_proplist_sets ( p , " alsa.subdevice_name " , sdn ) ;
pa_proplist_setf ( p , " alsa.device " , " %u " , snd_pcm_info_get_device ( pcm_info ) ) ;
if ( ( card = snd_pcm_info_get_card ( pcm_info ) ) > = 0 ) {
pa_proplist_setf ( p , " alsa.card " , " %i " , card ) ;
if ( snd_card_get_name ( card , & cn ) > = 0 )
pa_proplist_sets ( p , " alsa.card_name " , cn ) ;
if ( snd_card_get_longname ( card , & lcn ) > = 0 )
pa_proplist_sets ( p , " alsa.long_card_name " , lcn ) ;
}
if ( cn & & n )
pa_proplist_setf ( p , PA_PROP_DEVICE_DESCRIPTION , " %s - %s " , cn , n ) ;
else if ( cn )
pa_proplist_sets ( p , PA_PROP_DEVICE_DESCRIPTION , cn ) ;
else if ( n )
pa_proplist_sets ( p , PA_PROP_DEVICE_DESCRIPTION , n ) ;
free ( lcn ) ;
free ( cn ) ;
}
int pa_alsa_recover_from_poll ( snd_pcm_t * pcm , int revents ) {
snd_pcm_state_t state ;
int err ;
pa_assert ( pcm ) ;
if ( revents & POLLERR )
pa_log_warn ( " Got POLLERR from ALSA " ) ;
if ( revents & POLLNVAL )
pa_log_warn ( " Got POLLNVAL from ALSA " ) ;
if ( revents & POLLHUP )
pa_log_warn ( " Got POLLHUP from ALSA " ) ;
state = snd_pcm_state ( pcm ) ;
pa_log_warn ( " PCM state is %s " , snd_pcm_state_name ( state ) ) ;
/* Try to recover from this error */
switch ( state ) {
case SND_PCM_STATE_XRUN :
if ( ( err = snd_pcm_recover ( pcm , - EPIPE , 1 ) ) ! = 0 ) {
pa_log_warn ( " Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s " , snd_strerror ( err ) ) ;
return - 1 ;
}
break ;
case SND_PCM_STATE_SUSPENDED :
if ( ( err = snd_pcm_recover ( pcm , - ESTRPIPE , 1 ) ) ! = 0 ) {
pa_log_warn ( " Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s " , snd_strerror ( err ) ) ;
return - 1 ;
}
break ;
default :
snd_pcm_drop ( pcm ) ;
if ( ( err = snd_pcm_prepare ( pcm ) ) < 0 ) {
pa_log_warn ( " Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s " , snd_strerror ( err ) ) ;
return - 1 ;
}
break ;
}
return 0 ;
}
pa_rtpoll_item * pa_alsa_build_pollfd ( snd_pcm_t * pcm , pa_rtpoll * rtpoll ) {
int n , err ;
struct pollfd * pollfd ;
pa_rtpoll_item * item ;
pa_assert ( pcm ) ;
if ( ( n = snd_pcm_poll_descriptors_count ( pcm ) ) < 0 ) {
pa_log ( " snd_pcm_poll_descriptors_count() failed: %s " , snd_strerror ( n ) ) ;
return NULL ;
}
2008-08-19 22:39:54 +02:00
item = pa_rtpoll_item_new ( rtpoll , PA_RTPOLL_NEVER , ( unsigned ) n ) ;
2008-05-15 23:34:41 +00:00
pollfd = pa_rtpoll_item_get_pollfd ( item , NULL ) ;
2008-08-19 22:39:54 +02:00
if ( ( err = snd_pcm_poll_descriptors ( pcm , pollfd , ( unsigned ) n ) ) < 0 ) {
2008-05-15 23:34:41 +00:00
pa_log ( " snd_pcm_poll_descriptors() failed: %s " , snd_strerror ( err ) ) ;
pa_rtpoll_item_free ( item ) ;
return NULL ;
}
return item ;
}
2008-08-13 13:54:17 +02:00
pa_cvolume * pa_alsa_volume_divide ( pa_cvolume * r , const pa_cvolume * t ) {
unsigned i ;
pa_assert ( r ) ;
pa_assert ( t ) ;
pa_assert ( r - > channels = = t - > channels ) ;
for ( i = 0 ; i < r - > channels ; i + + ) {
double a , b , c ;
a = pa_sw_volume_to_linear ( r - > values [ i ] ) ; /* the hw volume */
b = pa_sw_volume_to_linear ( t - > values [ i ] ) ; /* the intended volume */
if ( a < = 0 )
c = 0 ;
else
c = b / a ;
r - > values [ i ] = pa_sw_volume_from_linear ( c ) ;
}
return r ;
}