2004-07-16 19:56:36 +00:00
/* $Id$ */
/***
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>
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>
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 {
int num_fds ;
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 ;
int polled ;
void ( * cb ) ( void * userdata ) ;
void * userdata ;
} ;
2006-04-18 19:31:50 +00:00
static void io_cb ( pa_mainloop_api * a , pa_io_event * e , PA_GCC_UNUSED int fd , pa_io_event_flags_t events , void * userdata ) {
2007-10-28 19:13:50 +00:00
struct pa_alsa_fdlist * fdl = userdata ;
2006-02-26 19:09:26 +00:00
int err , i ;
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 ;
fdl - > polled = 1 ;
memcpy ( fdl - > work_fds , fdl - > fds , sizeof ( struct pollfd ) * fdl - > num_fds ) ;
2007-10-28 19:13:50 +00: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
}
2006-04-18 19:31:50 +00:00
static void defer_cb ( pa_mainloop_api * a , PA_GCC_UNUSED pa_defer_event * e , void * userdata ) {
2007-10-28 19:13:50 +00:00
struct pa_alsa_fdlist * fdl = userdata ;
2006-02-26 19:09:26 +00:00
int num_fds , i , err ;
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 ) ;
2007-10-28 19:13:50 +00:00
num_fds = snd_mixer_poll_descriptors_count ( fdl - > mixer ) ;
pa_assert ( num_fds > 0 ) ;
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 ;
}
fdl - > polled = 0 ;
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 ;
fdl - > polled = 0 ;
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 ) {
int i ;
2007-10-28 19:13:50 +00:00
pa_assert ( fdl - > m ) ;
2006-02-26 19:09:26 +00: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 ,
pa_bool_t * use_mmap ,
pa_bool_t require_exact_channel_number ) {
2006-06-16 19:33:05 +00:00
int ret = - 1 ;
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 ;
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
buffer_size = * periods * * period_size ;
2007-01-04 13:43:45 +00:00
2007-10-28 19:13:50 +00:00
if ( ( ret = snd_pcm_hw_params_any ( pcm_handle , hwparams ) ) < 0 | |
( ret = snd_pcm_hw_params_set_rate_resample ( pcm_handle , hwparams , 0 ) ) < 0 )
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 ;
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 ;
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
if ( ( * period_size > 0 & & ( ret = snd_pcm_hw_params_set_period_size_near ( pcm_handle , hwparams , period_size , NULL ) ) < 0 ) | |
( * periods > 0 & & ( ret = snd_pcm_hw_params_set_buffer_size_near ( pcm_handle , hwparams , & buffer_size ) ) < 0 ) )
goto finish ;
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 ;
2006-03-05 18:35:45 +00:00
if ( ( ret = snd_pcm_hw_params_get_buffer_size ( hwparams , & buffer_size ) ) < 0 | |
( ret = snd_pcm_hw_params_get_period_size ( hwparams , period_size , NULL ) ) < 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 ;
ss - > channels = c ;
ss - > format = f ;
2007-10-28 19:13:50 +00:00
pa_assert ( buffer_size > 0 ) ;
pa_assert ( * period_size > 0 ) ;
2004-11-20 22:17:31 +00:00
* periods = buffer_size / * period_size ;
2007-10-28 19:13:50 +00:00
pa_assert ( * periods > 0 ) ;
2007-01-04 13:43:45 +00:00
2007-11-13 17:37:44 +00:00
if ( use_mmap )
* use_mmap = _use_mmap ;
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 ;
}
2007-10-28 19:13:50 +00:00
int pa_alsa_set_sw_params ( snd_pcm_t * pcm ) {
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 ;
}
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 ,
pa_bool_t * use_mmap ) {
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 ) ;
if ( ( err = snd_pcm_open ( & pcm_handle , d , mode , SND_PCM_NONBLOCK ) ) < 0 ) {
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 ;
if ( ( err = pa_alsa_set_hw_params ( pcm_handle , & try_ss , nfrags , period_size , use_mmap , TRUE ) ) < 0 ) {
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 ;
}
/* OK, we didn't find any good device, so let's try the raw hw: stuff */
d = pa_sprintf_malloc ( " hw:%s " , dev_id ) ;
pa_log_debug ( " Trying %s as last resort... " , d ) ;
pcm_handle = pa_alsa_open_by_device_string ( d , dev , ss , map , mode , nfrags , period_size , use_mmap ) ;
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 ,
pa_bool_t * use_mmap ) {
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 ( ; ; ) {
if ( ( err = snd_pcm_open ( & pcm_handle , d , mode , SND_PCM_NONBLOCK ) ) < 0 ) {
pa_log ( " Error opening PCM device %s: %s " , d , snd_strerror ( err ) ) ;
pa_xfree ( d ) ;
return NULL ;
}
if ( ( err = pa_alsa_set_hw_params ( pcm_handle , ss , nfrags , period_size , use_mmap , FALSE ) ) < 0 ) {
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 ;
if ( ss - > channels ! = map - > channels )
pa_channel_map_init_auto ( map , ss - > channels , PA_CHANNEL_MAP_ALSA ) ;
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 ) {
2006-08-18 21:38:40 +00:00
pa_log_warn ( " 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 ;
}
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 ) ) ) {
2006-08-18 21:38:40 +00:00
pa_log_warn ( " 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 ;
}