2023-02-08 18:12:00 +01:00
/* ALSA Card Profile */
/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */
/* SPDX-License-Identifier: MIT */
2020-05-15 19:42:15 +02:00
# include "acp.h"
# include "alsa-mixer.h"
# include "alsa-ucm.h"
2021-05-18 11:36:13 +10:00
# include <spa/utils/string.h>
2023-11-30 17:40:48 +01:00
# include <spa/utils/json.h>
2025-06-14 13:54:23 +03:00
# include <spa/utils/cleanup.h>
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
# include <spa/param/audio/iec958-types.h>
2025-10-08 16:38:51 -07:00
# include <spa/param/audio/raw.h>
2021-05-18 11:36:13 +10:00
2020-05-15 19:42:15 +02:00
int _acp_log_level = 1 ;
acp_log_func _acp_log_func ;
void * _acp_log_data ;
2021-04-14 18:02:15 +02:00
struct spa_i18n * acp_i18n ;
2025-04-28 09:45:04 +02:00
# define DEFAULT_CHANNELS 255u
2025-04-04 15:34:39 +02:00
# define DEFAULT_RATE 48000u
2022-11-15 16:30:26 +01:00
2020-07-06 17:22:42 +02:00
# define VOLUME_ACCURACY (PA_VOLUME_NORM / 100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
2020-11-23 15:31:10 +01:00
static const uint32_t channel_table [ PA_CHANNEL_POSITION_MAX ] = {
[ PA_CHANNEL_POSITION_MONO ] = ACP_CHANNEL_MONO ,
[ PA_CHANNEL_POSITION_FRONT_LEFT ] = ACP_CHANNEL_FL ,
[ PA_CHANNEL_POSITION_FRONT_RIGHT ] = ACP_CHANNEL_FR ,
[ PA_CHANNEL_POSITION_FRONT_CENTER ] = ACP_CHANNEL_FC ,
[ PA_CHANNEL_POSITION_REAR_CENTER ] = ACP_CHANNEL_RC ,
[ PA_CHANNEL_POSITION_REAR_LEFT ] = ACP_CHANNEL_RL ,
[ PA_CHANNEL_POSITION_REAR_RIGHT ] = ACP_CHANNEL_RR ,
[ PA_CHANNEL_POSITION_LFE ] = ACP_CHANNEL_LFE ,
[ PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ] = ACP_CHANNEL_FLC ,
[ PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER ] = ACP_CHANNEL_FRC ,
[ PA_CHANNEL_POSITION_SIDE_LEFT ] = ACP_CHANNEL_SL ,
[ PA_CHANNEL_POSITION_SIDE_RIGHT ] = ACP_CHANNEL_SR ,
2021-07-21 11:25:43 +02:00
[ PA_CHANNEL_POSITION_AUX0 ] = ACP_CHANNEL_START_Aux + 0 ,
[ PA_CHANNEL_POSITION_AUX1 ] = ACP_CHANNEL_START_Aux + 1 ,
[ PA_CHANNEL_POSITION_AUX2 ] = ACP_CHANNEL_START_Aux + 2 ,
[ PA_CHANNEL_POSITION_AUX3 ] = ACP_CHANNEL_START_Aux + 3 ,
[ PA_CHANNEL_POSITION_AUX4 ] = ACP_CHANNEL_START_Aux + 4 ,
[ PA_CHANNEL_POSITION_AUX5 ] = ACP_CHANNEL_START_Aux + 5 ,
[ PA_CHANNEL_POSITION_AUX6 ] = ACP_CHANNEL_START_Aux + 6 ,
[ PA_CHANNEL_POSITION_AUX7 ] = ACP_CHANNEL_START_Aux + 7 ,
[ PA_CHANNEL_POSITION_AUX8 ] = ACP_CHANNEL_START_Aux + 8 ,
[ PA_CHANNEL_POSITION_AUX9 ] = ACP_CHANNEL_START_Aux + 9 ,
[ PA_CHANNEL_POSITION_AUX10 ] = ACP_CHANNEL_START_Aux + 10 ,
[ PA_CHANNEL_POSITION_AUX11 ] = ACP_CHANNEL_START_Aux + 11 ,
[ PA_CHANNEL_POSITION_AUX12 ] = ACP_CHANNEL_START_Aux + 12 ,
2021-10-18 10:01:44 +02:00
[ PA_CHANNEL_POSITION_AUX13 ] = ACP_CHANNEL_START_Aux + 13 ,
2021-07-21 11:25:43 +02:00
[ PA_CHANNEL_POSITION_AUX14 ] = ACP_CHANNEL_START_Aux + 14 ,
[ PA_CHANNEL_POSITION_AUX15 ] = ACP_CHANNEL_START_Aux + 15 ,
[ PA_CHANNEL_POSITION_AUX16 ] = ACP_CHANNEL_START_Aux + 16 ,
[ PA_CHANNEL_POSITION_AUX17 ] = ACP_CHANNEL_START_Aux + 17 ,
[ PA_CHANNEL_POSITION_AUX18 ] = ACP_CHANNEL_START_Aux + 18 ,
[ PA_CHANNEL_POSITION_AUX19 ] = ACP_CHANNEL_START_Aux + 19 ,
[ PA_CHANNEL_POSITION_AUX20 ] = ACP_CHANNEL_START_Aux + 20 ,
[ PA_CHANNEL_POSITION_AUX21 ] = ACP_CHANNEL_START_Aux + 21 ,
[ PA_CHANNEL_POSITION_AUX22 ] = ACP_CHANNEL_START_Aux + 22 ,
[ PA_CHANNEL_POSITION_AUX23 ] = ACP_CHANNEL_START_Aux + 23 ,
[ PA_CHANNEL_POSITION_AUX24 ] = ACP_CHANNEL_START_Aux + 24 ,
[ PA_CHANNEL_POSITION_AUX25 ] = ACP_CHANNEL_START_Aux + 25 ,
[ PA_CHANNEL_POSITION_AUX26 ] = ACP_CHANNEL_START_Aux + 26 ,
[ PA_CHANNEL_POSITION_AUX27 ] = ACP_CHANNEL_START_Aux + 27 ,
[ PA_CHANNEL_POSITION_AUX28 ] = ACP_CHANNEL_START_Aux + 28 ,
[ PA_CHANNEL_POSITION_AUX29 ] = ACP_CHANNEL_START_Aux + 29 ,
[ PA_CHANNEL_POSITION_AUX30 ] = ACP_CHANNEL_START_Aux + 30 ,
[ PA_CHANNEL_POSITION_AUX31 ] = ACP_CHANNEL_START_Aux + 31 ,
2020-11-23 15:31:10 +01:00
[ PA_CHANNEL_POSITION_TOP_CENTER ] = ACP_CHANNEL_TC ,
[ PA_CHANNEL_POSITION_TOP_FRONT_LEFT ] = ACP_CHANNEL_TFL ,
[ PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ] = ACP_CHANNEL_TFR ,
[ PA_CHANNEL_POSITION_TOP_FRONT_CENTER ] = ACP_CHANNEL_TFC ,
[ PA_CHANNEL_POSITION_TOP_REAR_LEFT ] = ACP_CHANNEL_TRL ,
[ PA_CHANNEL_POSITION_TOP_REAR_RIGHT ] = ACP_CHANNEL_TRR ,
[ PA_CHANNEL_POSITION_TOP_REAR_CENTER ] = ACP_CHANNEL_TRC ,
} ;
2020-12-28 13:27:34 +01:00
static const char * channel_names [ ] = {
[ ACP_CHANNEL_UNKNOWN ] = " UNK " ,
[ ACP_CHANNEL_NA ] = " NA " ,
[ ACP_CHANNEL_MONO ] = " MONO " ,
[ ACP_CHANNEL_FL ] = " FL " ,
[ ACP_CHANNEL_FR ] = " FR " ,
[ ACP_CHANNEL_FC ] = " FC " ,
[ ACP_CHANNEL_LFE ] = " LFE " ,
[ ACP_CHANNEL_SL ] = " SL " ,
[ ACP_CHANNEL_SR ] = " SR " ,
[ ACP_CHANNEL_FLC ] = " FLC " ,
[ ACP_CHANNEL_FRC ] = " FRC " ,
[ ACP_CHANNEL_RC ] = " RC " ,
[ ACP_CHANNEL_RL ] = " RL " ,
[ ACP_CHANNEL_RR ] = " RR " ,
[ ACP_CHANNEL_TC ] = " TC " ,
[ ACP_CHANNEL_TFL ] = " TFL " ,
[ ACP_CHANNEL_TFC ] = " TFC " ,
[ ACP_CHANNEL_TFR ] = " TFR " ,
[ ACP_CHANNEL_TRL ] = " TRL " ,
[ ACP_CHANNEL_TRC ] = " TRC " ,
[ ACP_CHANNEL_TRR ] = " TRR " ,
[ ACP_CHANNEL_RLC ] = " RLC " ,
[ ACP_CHANNEL_RRC ] = " RRC " ,
[ ACP_CHANNEL_FLW ] = " FLW " ,
[ ACP_CHANNEL_FRW ] = " FRW " ,
[ ACP_CHANNEL_LFE2 ] = " LFE2 " ,
[ ACP_CHANNEL_FLH ] = " FLH " ,
[ ACP_CHANNEL_FCH ] = " FCH " ,
[ ACP_CHANNEL_FRH ] = " FRH " ,
[ ACP_CHANNEL_TFLC ] = " TFLC " ,
[ ACP_CHANNEL_TFRC ] = " TFRC " ,
[ ACP_CHANNEL_TSL ] = " TSL " ,
[ ACP_CHANNEL_TSR ] = " TSR " ,
[ ACP_CHANNEL_LLFE ] = " LLFE " ,
[ ACP_CHANNEL_RLFE ] = " RLFE " ,
[ ACP_CHANNEL_BC ] = " BC " ,
[ ACP_CHANNEL_BLC ] = " BLC " ,
[ ACP_CHANNEL_BRC ] = " BRC " ,
} ;
2020-11-26 10:42:26 +01:00
# define ACP_N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0]))
2020-11-23 15:31:10 +01:00
static inline uint32_t channel_pa2acp ( pa_channel_position_t channel )
{
2020-11-26 10:42:26 +01:00
if ( channel < 0 | | ( size_t ) channel > = ACP_N_ELEMENTS ( channel_table ) )
2020-11-23 15:31:10 +01:00
return ACP_CHANNEL_UNKNOWN ;
return channel_table [ channel ] ;
}
2020-12-28 13:27:34 +01:00
char * acp_channel_str ( char * buf , size_t len , enum acp_channel ch )
{
2021-07-21 11:25:43 +02:00
if ( ch > = ACP_CHANNEL_START_Aux & & ch < = ACP_CHANNEL_LAST_Aux ) {
snprintf ( buf , len , " AUX%d " , ch - ACP_CHANNEL_START_Aux ) ;
2020-12-28 13:27:34 +01:00
} else if ( ch > = ACP_CHANNEL_UNKNOWN & & ch < = ACP_CHANNEL_BRC ) {
snprintf ( buf , len , " %s " , channel_names [ ch ] ) ;
} else {
snprintf ( buf , len , " UNK " ) ;
}
return buf ;
}
2025-10-09 20:24:14 -07:00
static enum acp_channel acp_channel_from_str ( const char * buf , size_t len )
{
for ( unsigned long i = 0 ; i < ACP_N_ELEMENTS ( channel_names ) ; i + + ) {
if ( strncmp ( channel_names [ i ] , buf , len ) = = 0 )
return i ;
}
return ACP_CHANNEL_UNKNOWN ;
}
2020-12-28 13:27:34 +01:00
2020-10-01 13:12:19 +02:00
const char * acp_available_str ( enum acp_available status )
{
switch ( status ) {
case ACP_AVAILABLE_UNKNOWN :
return " unknown " ;
case ACP_AVAILABLE_NO :
return " no " ;
case ACP_AVAILABLE_YES :
return " yes " ;
}
return " error " ;
}
const char * acp_direction_str ( enum acp_direction direction )
{
switch ( direction ) {
case ACP_DIRECTION_CAPTURE :
return " capture " ;
case ACP_DIRECTION_PLAYBACK :
return " playback " ;
}
return " error " ;
}
2020-11-09 15:01:07 +01:00
static void port_free ( void * data )
2020-05-15 19:42:15 +02:00
{
2020-11-09 15:01:07 +01:00
pa_device_port * dp = data ;
pa_dynarray_clear ( & dp - > devices ) ;
pa_dynarray_clear ( & dp - > prof ) ;
pa_device_port_free ( dp ) ;
2020-05-15 19:42:15 +02:00
}
2020-11-09 15:01:07 +01:00
static void device_free ( void * data )
2020-05-15 19:42:15 +02:00
{
2020-11-09 15:01:07 +01:00
pa_alsa_device * dev = data ;
pa_dynarray_clear ( & dev - > port_array ) ;
pa_proplist_free ( dev - > proplist ) ;
pa_hashmap_free ( dev - > ports ) ;
2025-04-04 15:34:39 +02:00
free ( dev - > device . format . map ) ;
2020-05-15 19:42:15 +02:00
}
2021-10-18 10:53:07 +02:00
static inline void channelmap_to_acp ( pa_channel_map * m , uint32_t * map )
{
uint32_t i , j ;
for ( i = 0 ; i < m - > channels ; i + + ) {
map [ i ] = channel_pa2acp ( m - > map [ i ] ) ;
for ( j = 0 ; j < i ; j + + ) {
if ( map [ i ] = = map [ j ] )
map [ i ] + = 32 ;
}
}
}
2021-04-27 16:42:52 +02:00
static void init_device ( pa_card * impl , pa_alsa_device * dev , pa_alsa_direction_t direction ,
pa_alsa_mapping * m , uint32_t index )
2020-05-15 19:42:15 +02:00
{
2021-09-02 10:05:33 +02:00
char * * d ;
2020-05-15 19:42:15 +02:00
dev - > card = impl ;
dev - > mapping = m ;
dev - > device . index = index ;
dev - > device . name = m - > name ;
dev - > device . description = m - > description ;
dev - > device . priority = m - > priority ;
2020-08-18 12:31:03 +02:00
dev - > device . device_strings = ( const char * * ) m - > device_strings ;
2020-05-15 19:42:15 +02:00
dev - > device . format . format_mask = m - > sample_spec . format ;
dev - > device . format . rate_mask = m - > sample_spec . rate ;
dev - > device . format . channels = m - > channel_map . channels ;
2025-04-04 15:34:39 +02:00
dev - > device . format . map = calloc ( m - > channel_map . channels , sizeof ( uint32_t ) ) ;
channelmap_to_acp ( & m - > channel_map , dev - > device . format . map ) ;
2021-06-01 11:47:59 +02:00
pa_cvolume_reset ( & dev - > real_volume , dev - > device . format . channels ) ;
pa_cvolume_reset ( & dev - > soft_volume , dev - > device . format . channels ) ;
2020-05-15 19:42:15 +02:00
dev - > direction = direction ;
dev - > proplist = pa_proplist_new ( ) ;
pa_proplist_update ( dev - > proplist , PA_UPDATE_REPLACE , m - > proplist ) ;
if ( direction = = PA_ALSA_DIRECTION_OUTPUT ) {
dev - > mixer_path_set = m - > output_path_set ;
dev - > pcm_handle = m - > output_pcm ;
dev - > device . direction = ACP_DIRECTION_PLAYBACK ;
pa_proplist_update ( dev - > proplist , PA_UPDATE_REPLACE , m - > output_proplist ) ;
} else {
dev - > mixer_path_set = m - > input_path_set ;
dev - > pcm_handle = m - > input_pcm ;
dev - > device . direction = ACP_DIRECTION_CAPTURE ;
pa_proplist_update ( dev - > proplist , PA_UPDATE_REPLACE , m - > input_proplist ) ;
}
2024-12-07 13:27:09 +02:00
if ( m - > split ) {
2025-10-24 17:00:11 +02:00
char pos [ PA_CHANNELS_MAX * 8 ] ;
2024-12-07 13:27:09 +02:00
struct spa_strbuf b ;
int i ;
spa_strbuf_init ( & b , pos , sizeof ( pos ) ) ;
spa_strbuf_append ( & b , " [ " ) ;
for ( i = 0 ; i < m - > split - > channels ; + + i )
spa_strbuf_append ( & b , " %sAUX%d " , ( ( i = = 0 ) ? " " : " , " ) , m - > split - > idx [ i ] ) ;
spa_strbuf_append ( & b , " ] " ) ;
pa_proplist_sets ( dev - > proplist , " api.alsa.split.position " , pos ) ;
spa_strbuf_init ( & b , pos , sizeof ( pos ) ) ;
spa_strbuf_append ( & b , " [ " ) ;
for ( i = 0 ; i < m - > split - > hw_channels ; + + i )
spa_strbuf_append ( & b , " %sAUX%d " , ( ( i = = 0 ) ? " " : " , " ) , i ) ;
spa_strbuf_append ( & b , " ] " ) ;
pa_proplist_sets ( dev - > proplist , " api.alsa.split.hw-position " , pos ) ;
}
2021-04-27 16:42:52 +02:00
pa_proplist_sets ( dev - > proplist , PA_PROP_DEVICE_PROFILE_NAME , m - > name ) ;
pa_proplist_sets ( dev - > proplist , PA_PROP_DEVICE_PROFILE_DESCRIPTION , m - > description ) ;
2020-05-15 19:42:15 +02:00
pa_proplist_setf ( dev - > proplist , " card.profile.device " , " %u " , index ) ;
pa_proplist_as_dict ( dev - > proplist , & dev - > device . props ) ;
2020-11-09 15:01:07 +01:00
dev - > ports = pa_hashmap_new ( pa_idxset_string_hash_func ,
pa_idxset_string_compare_func ) ;
2021-06-02 17:25:21 +02:00
if ( m - > ucm_context . ucm ) {
2020-05-15 19:42:15 +02:00
dev - > ucm_context = & m - > ucm_context ;
2022-01-03 17:21:28 +01:00
if ( impl - > ucm . alib_prefix ! = NULL ) {
2021-06-02 17:25:21 +02:00
for ( d = m - > device_strings ; * d ; d + + ) {
2022-01-03 17:21:28 +01:00
if ( pa_startswith ( * d , impl - > ucm . alib_prefix ) ) {
size_t plen = strlen ( impl - > ucm . alib_prefix ) ;
2021-06-10 15:36:26 +02:00
size_t len = strlen ( * d ) ;
memmove ( * d , ( * d ) + plen , len - plen + 1 ) ;
2021-06-02 17:25:21 +02:00
dev - > device . flags | = ACP_DEVICE_UCM_DEVICE ;
}
}
}
}
2021-09-02 10:05:33 +02:00
for ( d = m - > device_strings ; * d ; d + + ) {
if ( pa_startswith ( * d , " iec958 " ) | |
pa_startswith ( * d , " hdmi " ) )
dev - > device . flags | = ACP_DEVICE_IEC958 ;
}
2020-05-15 19:42:15 +02:00
pa_dynarray_init ( & dev - > port_array , NULL ) ;
}
static int compare_profile ( const void * a , const void * b )
{
const pa_hashmap_item * i1 = a ;
const pa_hashmap_item * i2 = b ;
const pa_alsa_profile * p1 , * p2 ;
if ( i1 - > key = = NULL | | i2 - > key = = NULL )
return 0 ;
p1 = i1 - > value ;
p2 = i2 - > value ;
if ( p1 - > profile . priority = = 0 | | p2 - > profile . priority = = 0 )
return 0 ;
return p2 - > profile . priority - p1 - > profile . priority ;
}
2020-11-09 15:01:07 +01:00
static void profile_free ( void * data )
{
pa_alsa_profile * ap = data ;
pa_dynarray_clear ( & ap - > out . devices ) ;
if ( ap - > profile . flags & ACP_PROFILE_OFF ) {
free ( ap - > name ) ;
free ( ap - > description ) ;
free ( ap ) ;
}
}
2023-08-07 12:56:24 +02:00
static const char * find_best_verb ( pa_card * impl )
{
const char * res = NULL ;
unsigned prio = 0 ;
pa_alsa_ucm_verb * verb ;
PA_LLIST_FOREACH ( verb , impl - > ucm . verbs ) {
if ( verb - > priority > = prio )
res = pa_proplist_gets ( verb - > proplist , PA_ALSA_PROP_UCM_NAME ) ;
}
return res ;
}
2021-02-16 12:26:51 +01:00
static int add_pro_profile ( pa_card * impl , uint32_t index )
{
snd_ctl_t * ctl_hndl ;
2023-10-09 10:35:30 +02:00
int err , dev , count = 0 , n_capture = 0 , n_playback = 0 ;
2021-02-16 12:26:51 +01:00
pa_alsa_profile * ap ;
pa_alsa_profile_set * ps = impl - > profile_set ;
pa_alsa_mapping * m ;
char * device ;
snd_pcm_info_t * pcminfo ;
pa_sample_spec ss ;
snd_pcm_uframes_t try_period_size , try_buffer_size ;
2023-10-09 10:35:30 +02:00
uint32_t idx ;
2021-02-16 12:26:51 +01:00
2023-08-07 12:56:24 +02:00
if ( impl - > use_ucm ) {
const char * verb = find_best_verb ( impl ) ;
if ( verb = = NULL )
return - ENOTSUP ;
if ( ( err = snd_use_case_set ( impl - > ucm . ucm_mgr , " _verb " , verb ) ) < 0 ) {
pa_log_error ( " error setting verb: %s " , snd_strerror ( err ) ) ;
return err ;
}
}
2021-02-16 12:26:51 +01:00
ss . format = PA_SAMPLE_S32LE ;
2022-12-16 12:57:17 +01:00
ss . rate = impl - > rate ;
2023-01-31 15:59:39 +01:00
ss . channels = impl - > pro_channels ;
2021-02-16 12:26:51 +01:00
ap = pa_xnew0 ( pa_alsa_profile , 1 ) ;
ap - > profile_set = ps ;
ap - > profile . name = ap - > name = pa_xstrdup ( " pro-audio " ) ;
ap - > profile . description = ap - > description = pa_xstrdup ( _ ( " Pro Audio " ) ) ;
ap - > profile . available = ACP_AVAILABLE_YES ;
2023-01-03 13:13:03 +01:00
ap - > profile . flags = ACP_PROFILE_PRO ;
2021-02-16 12:26:51 +01:00
ap - > output_mappings = pa_idxset_new ( pa_idxset_trivial_hash_func , pa_idxset_trivial_compare_func ) ;
ap - > input_mappings = pa_idxset_new ( pa_idxset_trivial_hash_func , pa_idxset_trivial_compare_func ) ;
pa_hashmap_put ( ps - > profiles , ap - > name , ap ) ;
ap - > output_name = pa_xstrdup ( " pro-output " ) ;
ap - > input_name = pa_xstrdup ( " pro-input " ) ;
ap - > priority = 1 ;
2021-03-13 15:35:38 +01:00
pa_assert_se ( asprintf ( & device , " hw:%d " , index ) > = 0 ) ;
2021-02-16 12:26:51 +01:00
if ( ( err = snd_ctl_open ( & ctl_hndl , device , 0 ) ) < 0 ) {
pa_log_error ( " can't open control for card %s: %s " ,
device , snd_strerror ( err ) ) ;
2021-07-08 14:34:09 -04:00
free ( device ) ;
2021-02-16 12:26:51 +01:00
return err ;
}
2021-07-08 14:34:09 -04:00
free ( device ) ;
2021-02-16 12:26:51 +01:00
snd_pcm_info_alloca ( & pcminfo ) ;
dev = - 1 ;
while ( 1 ) {
2025-06-14 13:54:23 +03:00
char desc [ 128 ] , devstr [ 128 ] ;
2021-02-16 20:21:53 +01:00
2021-02-16 12:26:51 +01:00
if ( ( err = snd_ctl_pcm_next_device ( ctl_hndl , & dev ) ) < 0 ) {
pa_log_error ( " error iterating devices: %s " , snd_strerror ( err ) ) ;
break ;
}
if ( dev < 0 )
break ;
snd_pcm_info_set_device ( pcminfo , dev ) ;
snd_pcm_info_set_subdevice ( pcminfo , 0 ) ;
2021-02-16 20:21:53 +01:00
snprintf ( devstr , sizeof ( devstr ) , " hw:%d,%d " , index , dev ) ;
if ( count + + = = 0 )
snprintf ( desc , sizeof ( desc ) , " Pro " ) ;
else
snprintf ( desc , sizeof ( desc ) , " Pro %d " , dev ) ;
2021-02-16 12:26:51 +01:00
snd_pcm_info_set_stream ( pcminfo , SND_PCM_STREAM_PLAYBACK ) ;
if ( ( err = snd_ctl_pcm_info ( ctl_hndl , pcminfo ) ) < 0 ) {
if ( err ! = - ENOENT )
pa_log_error ( " error pcm info: %s " , snd_strerror ( err ) ) ;
}
if ( err > = 0 ) {
2025-06-14 13:54:23 +03:00
spa_autofree char * name = NULL ;
2021-03-13 15:35:38 +01:00
pa_assert_se ( asprintf ( & name , " Mapping pro-output-%d " , dev ) > = 0 ) ;
2021-02-16 12:26:51 +01:00
m = pa_alsa_mapping_get ( ps , name ) ;
2025-06-14 13:54:23 +03:00
} else {
m = NULL ;
}
if ( m ) {
2021-02-16 20:21:53 +01:00
m - > description = pa_xstrdup ( desc ) ;
2021-02-16 12:26:51 +01:00
m - > device_strings = pa_split_spaces_strv ( devstr ) ;
try_period_size = 1024 ;
try_buffer_size = 1024 * 64 ;
m - > sample_spec = ss ;
if ( ( m - > output_pcm = pa_alsa_open_by_template ( m - > device_strings ,
devstr , NULL , & m - > sample_spec ,
& m - > channel_map , SND_PCM_STREAM_PLAYBACK ,
& try_period_size , & try_buffer_size ,
2023-03-20 18:22:09 +01:00
0 , NULL , NULL , NULL , NULL , false ) ) ) {
2021-02-16 12:26:51 +01:00
pa_alsa_init_proplist_pcm ( NULL , m - > output_proplist , m - > output_pcm ) ;
2022-06-08 17:03:50 +02:00
pa_proplist_setf ( m - > output_proplist , " clock.name " , " api.alsa.%u " , index ) ;
2023-08-28 10:06:29 +02:00
pa_proplist_setf ( m - > output_proplist , " device.profile.pro " , " true " ) ;
2022-08-05 12:31:28 +02:00
pa_alsa_close ( & m - > output_pcm ) ;
2021-02-16 12:26:51 +01:00
m - > supported = true ;
2021-10-18 10:51:48 +02:00
pa_channel_map_init_auto ( & m - > channel_map , m - > sample_spec . channels , PA_CHANNEL_MAP_AUX ) ;
2023-10-09 10:35:30 +02:00
n_playback + + ;
2021-02-16 12:26:51 +01:00
}
pa_idxset_put ( ap - > output_mappings , m , NULL ) ;
}
snd_pcm_info_set_stream ( pcminfo , SND_PCM_STREAM_CAPTURE ) ;
if ( ( err = snd_ctl_pcm_info ( ctl_hndl , pcminfo ) ) < 0 ) {
if ( err ! = - ENOENT )
pa_log_error ( " error pcm info: %s " , snd_strerror ( err ) ) ;
}
if ( err > = 0 ) {
2025-06-14 13:54:23 +03:00
spa_autofree char * name = NULL ;
2021-03-13 15:35:38 +01:00
pa_assert_se ( asprintf ( & name , " Mapping pro-input-%d " , dev ) > = 0 ) ;
2021-02-16 12:26:51 +01:00
m = pa_alsa_mapping_get ( ps , name ) ;
2025-06-14 13:54:23 +03:00
} else {
m = NULL ;
}
if ( m ) {
2021-02-16 20:21:53 +01:00
m - > description = pa_xstrdup ( desc ) ;
2021-02-16 12:26:51 +01:00
m - > device_strings = pa_split_spaces_strv ( devstr ) ;
try_period_size = 1024 ;
try_buffer_size = 1024 * 64 ;
m - > sample_spec = ss ;
if ( ( m - > input_pcm = pa_alsa_open_by_template ( m - > device_strings ,
devstr , NULL , & m - > sample_spec ,
& m - > channel_map , SND_PCM_STREAM_CAPTURE ,
& try_period_size , & try_buffer_size ,
2023-03-20 18:22:09 +01:00
0 , NULL , NULL , NULL , NULL , false ) ) ) {
2021-02-16 12:26:51 +01:00
pa_alsa_init_proplist_pcm ( NULL , m - > input_proplist , m - > input_pcm ) ;
2022-06-08 17:03:50 +02:00
pa_proplist_setf ( m - > input_proplist , " clock.name " , " api.alsa.%u " , index ) ;
2023-08-28 10:06:29 +02:00
pa_proplist_setf ( m - > input_proplist , " device.profile.pro " , " true " ) ;
2022-08-05 12:31:28 +02:00
pa_alsa_close ( & m - > input_pcm ) ;
2021-02-16 12:26:51 +01:00
m - > supported = true ;
2021-10-18 10:51:48 +02:00
pa_channel_map_init_auto ( & m - > channel_map , m - > sample_spec . channels , PA_CHANNEL_MAP_AUX ) ;
2023-10-09 10:35:30 +02:00
n_capture + + ;
2021-02-16 12:26:51 +01:00
}
pa_idxset_put ( ap - > input_mappings , m , NULL ) ;
}
}
snd_ctl_close ( ctl_hndl ) ;
2025-09-06 18:15:29 +03:00
/* FireWire ALSA driver latency is determined by the buffer size and not the
* period . Timer - based scheduling is then not really useful on these devices as
* the latency is fixed . Enable IRQ scheduling unconditionally for these devices ,
* so that controlling the latency works properly .
*/
bool is_firewire = spa_streq ( pa_proplist_gets ( impl - > proplist , " device.bus " ) , " firewire " ) ;
if ( ( n_capture = = 1 & & n_playback = = 1 ) | | is_firewire ) {
2023-10-09 10:35:30 +02:00
PA_IDXSET_FOREACH ( m , ap - > output_mappings , idx ) {
pa_proplist_setf ( m - > output_proplist , " node.group " , " pro-audio-%u " , index ) ;
pa_proplist_setf ( m - > output_proplist , " node.link-group " , " pro-audio-%u " , index ) ;
pa_proplist_setf ( m - > output_proplist , " api.alsa.auto-link " , " true " ) ;
2023-10-09 12:28:10 +02:00
pa_proplist_setf ( m - > output_proplist , " api.alsa.disable-tsched " , " true " ) ;
2023-10-09 10:35:30 +02:00
}
PA_IDXSET_FOREACH ( m , ap - > input_mappings , idx ) {
pa_proplist_setf ( m - > input_proplist , " node.group " , " pro-audio-%u " , index ) ;
pa_proplist_setf ( m - > input_proplist , " node.link-group " , " pro-audio-%u " , index ) ;
pa_proplist_setf ( m - > input_proplist , " api.alsa.auto-link " , " true " ) ;
2023-10-09 12:28:10 +02:00
pa_proplist_setf ( m - > input_proplist , " api.alsa.disable-tsched " , " true " ) ;
2023-10-09 10:35:30 +02:00
}
}
2021-02-16 12:26:51 +01:00
return 0 ;
}
2023-11-30 17:40:48 +01:00
static bool contains_string ( const char * arr , const char * str )
{
2024-09-13 13:09:54 +02:00
struct spa_json it [ 1 ] ;
2023-11-30 17:40:48 +01:00
char v [ 256 ] ;
if ( arr = = NULL | | str = = NULL )
return false ;
2024-09-13 13:09:54 +02:00
if ( spa_json_begin_array_relax ( & it [ 0 ] , arr , strlen ( arr ) ) < = 0 )
return false ;
2023-11-30 17:40:48 +01:00
2024-09-13 13:09:54 +02:00
while ( spa_json_get_string ( & it [ 0 ] , v , sizeof ( v ) ) > 0 ) {
2023-11-30 17:40:48 +01:00
if ( spa_streq ( v , str ) )
return true ;
}
return false ;
}
2021-02-16 12:26:51 +01:00
2020-05-15 19:42:15 +02:00
static void add_profiles ( pa_card * impl )
{
pa_alsa_profile * ap ;
void * state ;
struct acp_card_profile * cp ;
pa_device_port * dp ;
pa_alsa_device * dev ;
int n_profiles , n_ports , n_devices ;
uint32_t idx ;
2023-11-30 17:40:48 +01:00
const char * arr ;
2025-03-13 19:23:32 +02:00
bool broken_ucm = false ;
2020-05-15 19:42:15 +02:00
n_devices = 0 ;
2020-11-09 15:01:07 +01:00
pa_dynarray_init ( & impl - > out . devices , device_free ) ;
2020-05-15 19:42:15 +02:00
ap = pa_xnew0 ( pa_alsa_profile , 1 ) ;
2020-08-18 12:31:03 +02:00
ap - > profile . name = ap - > name = pa_xstrdup ( " off " ) ;
ap - > profile . description = ap - > description = pa_xstrdup ( _ ( " Off " ) ) ;
2020-05-15 19:42:15 +02:00
ap - > profile . available = ACP_AVAILABLE_YES ;
2020-09-28 11:32:36 +02:00
ap - > profile . flags = ACP_PROFILE_OFF ;
2020-08-18 12:31:03 +02:00
pa_hashmap_put ( impl - > profiles , ap - > name , ap ) ;
2020-05-15 19:42:15 +02:00
2025-07-02 08:09:54 -04:00
if ( ! impl - > disable_pro_audio )
add_pro_profile ( impl , impl - > card . index ) ;
2021-02-16 12:26:51 +01:00
2020-05-15 19:42:15 +02:00
PA_HASHMAP_FOREACH ( ap , impl - > profile_set - > profiles , state ) {
pa_alsa_mapping * m ;
cp = & ap - > profile ;
2020-08-18 12:31:03 +02:00
cp - > name = ap - > name ;
cp - > description = ap - > description ;
cp - > priority = ap - > priority ? ap - > priority : 1 ;
2020-05-15 19:42:15 +02:00
pa_dynarray_init ( & ap - > out . devices , NULL ) ;
if ( ap - > output_mappings ) {
PA_IDXSET_FOREACH ( m , ap - > output_mappings , idx ) {
dev = & m - > output ;
if ( dev - > mapping = = NULL ) {
2021-04-27 16:42:52 +02:00
init_device ( impl , dev , PA_ALSA_DIRECTION_OUTPUT , m , n_devices + + ) ;
2020-05-15 19:42:15 +02:00
pa_dynarray_append ( & impl - > out . devices , dev ) ;
}
2020-12-09 00:25:09 +01:00
if ( impl - > use_ucm ) {
2023-11-27 23:52:45 +03:00
if ( m - > ucm_context . ucm_device ) {
2023-11-27 23:30:36 +03:00
pa_alsa_ucm_add_port ( NULL , & m - > ucm_context ,
2023-01-03 13:16:06 +01:00
true , impl - > ports , ap , NULL ) ;
pa_alsa_ucm_add_ports ( & dev - > ports , m - > proplist , & m - > ucm_context ,
true , impl , dev - > pcm_handle , impl - > profile_set - > ignore_dB ) ;
}
2020-12-09 00:25:09 +01:00
}
2020-05-15 19:42:15 +02:00
else
pa_alsa_path_set_add_ports ( m - > output_path_set , ap , impl - > ports ,
dev - > ports , NULL ) ;
pa_dynarray_append ( & ap - > out . devices , dev ) ;
2025-03-13 19:23:32 +02:00
if ( m - > split & & m - > split - > broken )
broken_ucm = true ;
2020-05-15 19:42:15 +02:00
}
}
if ( ap - > input_mappings ) {
PA_IDXSET_FOREACH ( m , ap - > input_mappings , idx ) {
dev = & m - > input ;
if ( dev - > mapping = = NULL ) {
2021-04-27 16:42:52 +02:00
init_device ( impl , dev , PA_ALSA_DIRECTION_INPUT , m , n_devices + + ) ;
2020-05-15 19:42:15 +02:00
pa_dynarray_append ( & impl - > out . devices , dev ) ;
}
2020-12-09 00:25:09 +01:00
if ( impl - > use_ucm ) {
2023-11-27 23:52:45 +03:00
if ( m - > ucm_context . ucm_device ) {
2023-11-27 23:30:36 +03:00
pa_alsa_ucm_add_port ( NULL , & m - > ucm_context ,
2023-01-03 13:16:06 +01:00
false , impl - > ports , ap , NULL ) ;
pa_alsa_ucm_add_ports ( & dev - > ports , m - > proplist , & m - > ucm_context ,
false , impl , dev - > pcm_handle , impl - > profile_set - > ignore_dB ) ;
}
2020-12-09 00:25:09 +01:00
} else
2020-05-15 19:42:15 +02:00
pa_alsa_path_set_add_ports ( m - > input_path_set , ap , impl - > ports ,
dev - > ports , NULL ) ;
pa_dynarray_append ( & ap - > out . devices , dev ) ;
2025-03-13 19:23:32 +02:00
if ( m - > split & & m - > split - > broken )
broken_ucm = true ;
2020-05-15 19:42:15 +02:00
}
}
cp - > n_devices = pa_dynarray_size ( & ap - > out . devices ) ;
cp - > devices = ap - > out . devices . array . data ;
2020-08-18 12:31:03 +02:00
pa_hashmap_put ( impl - > profiles , ap - > name , cp ) ;
2020-05-15 19:42:15 +02:00
}
2025-03-13 19:23:32 +02:00
/* Add a conspicuous notice if there are errors in the UCM profile */
if ( broken_ucm ) {
const char * desc ;
char * new_desc = NULL ;
desc = pa_proplist_gets ( impl - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ;
if ( ! desc )
desc = " " ;
new_desc = spa_aprintf ( _ ( " %s [ALSA UCM error] " ) , desc ) ;
pa_log_notice ( " Errors in ALSA UCM profile for card %s " , desc ) ;
if ( new_desc )
pa_proplist_sets ( impl - > proplist , PA_PROP_DEVICE_DESCRIPTION , new_desc ) ;
free ( new_desc ) ;
}
2020-05-15 19:42:15 +02:00
pa_dynarray_init ( & impl - > out . ports , NULL ) ;
n_ports = 0 ;
PA_HASHMAP_FOREACH ( dp , impl - > ports , state ) {
void * state2 ;
dp - > card = impl ;
dp - > port . index = n_ports + + ;
2020-08-18 12:31:03 +02:00
dp - > port . priority = dp - > priority ;
2020-05-15 19:42:15 +02:00
pa_dynarray_init ( & dp - > prof , NULL ) ;
2020-08-10 14:25:03 +02:00
pa_dynarray_init ( & dp - > devices , NULL ) ;
2020-05-15 19:42:15 +02:00
n_profiles = 0 ;
PA_HASHMAP_FOREACH ( cp , dp - > profiles , state2 ) {
pa_dynarray_append ( & dp - > prof , cp ) ;
n_profiles + + ;
}
dp - > port . n_profiles = n_profiles ;
dp - > port . profiles = dp - > prof . array . data ;
pa_proplist_setf ( dp - > proplist , " card.profile.port " , " %u " , dp - > port . index ) ;
pa_proplist_as_dict ( dp - > proplist , & dp - > port . props ) ;
pa_dynarray_append ( & impl - > out . ports , dp ) ;
}
2020-08-10 14:25:03 +02:00
PA_DYNARRAY_FOREACH ( dev , & impl - > out . devices , idx ) {
PA_HASHMAP_FOREACH ( dp , dev - > ports , state ) {
pa_dynarray_append ( & dev - > port_array , dp ) ;
pa_dynarray_append ( & dp - > devices , dev ) ;
}
dev - > device . ports = dev - > port_array . array . data ;
dev - > device . n_ports = pa_dynarray_size ( & dev - > port_array ) ;
}
2023-11-30 17:40:48 +01:00
arr = pa_proplist_gets ( impl - > proplist , " api.acp.hidden-ports " ) ;
2020-08-10 14:25:03 +02:00
PA_HASHMAP_FOREACH ( dp , impl - > ports , state ) {
2023-11-30 17:40:48 +01:00
if ( contains_string ( arr , dp - > name ) )
dp - > port . flags | = ACP_PORT_HIDDEN ;
2020-08-10 14:25:03 +02:00
dp - > port . devices = dp - > devices . array . data ;
dp - > port . n_devices = pa_dynarray_size ( & dp - > devices ) ;
}
2020-05-15 19:42:15 +02:00
pa_hashmap_sort ( impl - > profiles , compare_profile ) ;
n_profiles = 0 ;
pa_dynarray_init ( & impl - > out . profiles , NULL ) ;
2023-11-30 17:40:48 +01:00
arr = pa_proplist_gets ( impl - > proplist , " api.acp.hidden-profiles " ) ;
2020-05-15 19:42:15 +02:00
PA_HASHMAP_FOREACH ( cp , impl - > profiles , state ) {
2023-11-30 17:40:48 +01:00
if ( contains_string ( arr , cp - > name ) )
cp - > flags | = ACP_PROFILE_HIDDEN ;
2020-05-15 19:42:15 +02:00
cp - > index = n_profiles + + ;
pa_dynarray_append ( & impl - > out . profiles , cp ) ;
}
}
2020-09-08 16:39:47 +02:00
static pa_available_t calc_port_state ( pa_device_port * p , pa_card * impl )
{
void * state ;
pa_alsa_jack * jack ;
pa_available_t pa = PA_AVAILABLE_UNKNOWN ;
pa_device_port * port ;
PA_HASHMAP_FOREACH ( jack , impl - > jacks , state ) {
pa_available_t cpa ;
if ( impl - > use_ucm )
port = pa_hashmap_get ( impl - > ports , jack - > name ) ;
else {
if ( jack - > path )
port = jack - > path - > port ;
else
continue ;
}
if ( p ! = port )
continue ;
cpa = jack - > plugged_in ? jack - > state_plugged : jack - > state_unplugged ;
if ( cpa = = PA_AVAILABLE_NO ) {
/* If a plugged-in jack causes the availability to go to NO, it
* should override all other availability information ( like a
* blacklist ) so set and bail */
if ( jack - > plugged_in ) {
pa = cpa ;
break ;
}
2021-04-28 20:29:44 +02:00
/* If the current availability is unknown go the more precise no,
2020-09-08 16:39:47 +02:00
* but otherwise don ' t change state */
if ( pa = = PA_AVAILABLE_UNKNOWN )
pa = cpa ;
} else if ( cpa = = PA_AVAILABLE_YES ) {
/* Output is available through at least one jack, so go to that
* level of availability . We still need to continue iterating through
* the jacks in case a jack is plugged in that forces the state to no
*/
pa = cpa ;
}
}
return pa ;
2020-05-15 19:42:15 +02:00
}
static void profile_set_available ( pa_card * impl , uint32_t index ,
2020-10-01 13:13:02 +02:00
enum acp_available status , bool emit )
2020-05-15 19:42:15 +02:00
{
struct acp_card_profile * p = impl - > card . profiles [ index ] ;
enum acp_available old = p - > available ;
2020-10-01 13:13:02 +02:00
if ( old ! = status )
2020-10-01 13:26:06 +02:00
pa_log_info ( " Profile %s available %s -> %s " , p - > name ,
2020-10-01 13:13:02 +02:00
acp_available_str ( old ) , acp_available_str ( status ) ) ;
2020-05-15 19:42:15 +02:00
p - > available = status ;
2021-03-27 20:36:43 +01:00
if ( emit & & impl - > events & & impl - > events - > profile_available )
2020-05-15 19:42:15 +02:00
impl - > events - > profile_available ( impl - > user_data , index ,
old , status ) ;
}
struct temp_port_avail {
2020-09-08 16:39:47 +02:00
pa_device_port * port ;
pa_available_t avail ;
2020-05-15 19:42:15 +02:00
} ;
static int report_jack_state ( snd_mixer_elem_t * melem , unsigned int mask )
{
2020-09-08 16:39:47 +02:00
pa_card * impl = snd_mixer_elem_get_callback_private ( melem ) ;
2023-03-20 17:47:52 +01:00
snd_hctl_elem_t * * _elem = snd_mixer_elem_get_private ( melem ) , * elem ;
2020-09-08 16:39:47 +02:00
snd_ctl_elem_value_t * elem_value ;
2022-10-29 20:22:06 +02:00
bool plugged_in , any_input_port_available ;
2020-09-08 16:39:47 +02:00
void * state ;
pa_alsa_jack * jack ;
struct temp_port_avail * tp , * tports ;
pa_alsa_profile * profile ;
enum acp_available active_available = ACP_AVAILABLE_UNKNOWN ;
size_t size ;
2020-05-15 19:42:15 +02:00
2023-03-20 17:47:52 +01:00
pa_assert ( _elem ) ;
elem = * _elem ;
2020-05-15 19:42:15 +02:00
#if 0
2020-09-08 16:39:47 +02:00
/* Changing the jack state may cause a port change, and a port change will
* make the sink or source change the mixer settings . If there are multiple
* users having pulseaudio running , the mixer changes done by inactive
* users may mess up the volume settings for the active users , because when
* the inactive users change the mixer settings , those changes are picked
* up by the active user ' s pulseaudio instance and the changes are
* interpreted as if the active user changed the settings manually e . g .
* with alsamixer . Even single - user systems suffer from this , because gdm
* runs its own pulseaudio instance .
*
* We rerun this function when being unsuspended to catch up on jack state
* changes */
if ( u - > card - > suspend_cause & PA_SUSPEND_SESSION )
return 0 ;
2020-05-15 19:42:15 +02:00
# endif
2020-09-08 16:39:47 +02:00
if ( mask = = SND_CTL_EVENT_MASK_REMOVE )
return 0 ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
snd_ctl_elem_value_alloca ( & elem_value ) ;
if ( snd_hctl_elem_read ( elem , elem_value ) < 0 ) {
pa_log_warn ( " Failed to read jack detection from '%s' " , pa_strnull ( snd_hctl_elem_get_name ( elem ) ) ) ;
return 0 ;
}
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
plugged_in = ! ! snd_ctl_elem_value_get_boolean ( elem_value , 0 ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
pa_log_debug ( " Jack '%s' is now %s " , pa_strnull ( snd_hctl_elem_get_name ( elem ) ) ,
plugged_in ? " plugged in " : " unplugged " ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
size = sizeof ( struct temp_port_avail ) * ( pa_hashmap_size ( impl - > jacks ) + 1 ) ;
tports = tp = alloca ( size ) ;
memset ( tports , 0 , size ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
PA_HASHMAP_FOREACH ( jack , impl - > jacks , state )
if ( jack - > melem = = melem ) {
pa_alsa_jack_set_plugged_in ( jack , plugged_in ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( impl - > use_ucm ) {
/* When using UCM, pa_alsa_jack_set_plugged_in() maps the jack
* state to port availability . */
continue ;
}
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
/* When not using UCM, we have to do the jack state -> port
* availability mapping ourselves . */
pa_assert_se ( tp - > port = jack - > path - > port ) ;
tp - > avail = calc_port_state ( tp - > port , impl ) ;
tp + + ;
}
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
/* Report available ports before unavailable ones: in case port 1
* becomes available when port 2 becomes unavailable ,
* this prevents an unnecessary switch port 1 - > port 3 - > port 2 */
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
for ( tp = tports ; tp - > port ; tp + + )
if ( tp - > avail ! = PA_AVAILABLE_NO )
pa_device_port_set_available ( tp - > port , tp - > avail ) ;
for ( tp = tports ; tp - > port ; tp + + )
if ( tp - > avail = = PA_AVAILABLE_NO )
pa_device_port_set_available ( tp - > port , tp - > avail ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
for ( tp = tports ; tp - > port ; tp + + ) {
pa_alsa_port_data * data ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
data = PA_DEVICE_PORT_DATA ( tp - > port ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( ! data - > suspend_when_unavailable )
continue ;
2020-05-15 19:42:15 +02:00
#if 0
2020-09-08 16:39:47 +02:00
pa_sink * sink ;
uint32_t idx ;
PA_IDXSET_FOREACH ( sink , u - > core - > sinks , idx ) {
if ( sink - > active_port = = tp - > port )
pa_sink_suspend ( sink , tp - > avail = = PA_AVAILABLE_NO , PA_SUSPEND_UNAVAILABLE ) ;
}
2020-05-15 19:42:15 +02:00
# endif
2020-09-08 16:39:47 +02:00
}
/* Update profile availabilities. Ideally we would mark all profiles
* unavailable that contain unavailable devices . We can ' t currently do that
* in all cases , because if there are multiple sinks in a profile , and the
* profile contains a mix of available and unavailable ports , we don ' t know
* how the ports are distributed between the different sinks . It ' s possible
* that some sinks contain only unavailable ports , in which case we should
* mark the profile as unavailable , but it ' s also possible that all sinks
* contain at least one available port , in which case we should mark the
* profile as available . Until the data structures are improved so that we
* can distinguish between these two cases , we mark the problematic cases
* as available ( well , " unknown " to be precise , but there ' s little
* practical difference ) .
*
* When all output ports are unavailable , we know that all sinks are
* unavailable , and therefore the profile is marked unavailable as well .
* The same applies to input ports as well , of course .
*
* If there are no output ports at all , but the profile contains at least
* one sink , then the output is considered to be available . */
if ( impl - > card . active_profile_index ! = ACP_INVALID_INDEX )
active_available = impl - > card . profiles [ impl - > card . active_profile_index ] - > available ;
2022-10-29 20:22:06 +02:00
/* First round - detect, if we have any input port available.
If the hardware can report the state for all I / O jacks , only speakers
may be plugged in . */
any_input_port_available = false ;
PA_HASHMAP_FOREACH ( profile , impl - > profiles , state ) {
pa_device_port * port ;
void * state2 ;
if ( profile - > profile . flags & ACP_PROFILE_OFF )
continue ;
PA_HASHMAP_FOREACH ( port , impl - > ports , state2 ) {
if ( ! pa_hashmap_get ( port - > profiles , profile - > profile . name ) )
continue ;
if ( port - > port . direction = = ACP_DIRECTION_CAPTURE & &
port - > port . available ! = ACP_AVAILABLE_NO ) {
any_input_port_available = true ;
goto input_port_found ;
}
}
}
input_port_found :
/* Second round */
2020-09-08 16:39:47 +02:00
PA_HASHMAP_FOREACH ( profile , impl - > profiles , state ) {
pa_device_port * port ;
void * state2 ;
bool has_input_port = false ;
bool has_output_port = false ;
bool found_available_input_port = false ;
bool found_available_output_port = false ;
enum acp_available available = ACP_AVAILABLE_UNKNOWN ;
2020-10-01 13:13:02 +02:00
if ( profile - > profile . flags & ACP_PROFILE_OFF )
continue ;
2020-09-08 16:39:47 +02:00
PA_HASHMAP_FOREACH ( port , impl - > ports , state2 ) {
if ( ! pa_hashmap_get ( port - > profiles , profile - > profile . name ) )
continue ;
if ( port - > port . direction = = ACP_DIRECTION_CAPTURE ) {
has_input_port = true ;
if ( port - > port . available ! = ACP_AVAILABLE_NO )
found_available_input_port = true ;
} else {
has_output_port = true ;
if ( port - > port . available ! = ACP_AVAILABLE_NO )
found_available_output_port = true ;
}
}
if ( ( has_input_port & & ! found_available_input_port ) | |
( has_output_port & & ! found_available_output_port ) )
available = ACP_AVAILABLE_NO ;
if ( has_input_port & & ! has_output_port & & found_available_input_port )
available = ACP_AVAILABLE_YES ;
2022-10-29 20:22:06 +02:00
if ( has_output_port & & ( ! has_input_port | | ! any_input_port_available ) & & found_available_output_port )
2020-09-08 16:39:47 +02:00
available = ACP_AVAILABLE_YES ;
if ( has_output_port & & has_input_port & & found_available_output_port & & found_available_input_port )
available = ACP_AVAILABLE_YES ;
/* We want to update the active profile's status last, so logic that
* may change the active profile based on profile availability status
* has an updated view of all profiles ' availabilities . */
if ( profile - > profile . index = = impl - > card . active_profile_index )
active_available = available ;
else
2020-10-01 13:13:02 +02:00
profile_set_available ( impl , profile - > profile . index , available , false ) ;
2020-09-08 16:39:47 +02:00
}
if ( impl - > card . active_profile_index ! = ACP_INVALID_INDEX )
2020-10-01 13:13:02 +02:00
profile_set_available ( impl , impl - > card . active_profile_index , active_available , true ) ;
2020-09-08 16:39:47 +02:00
return 0 ;
2020-05-15 19:42:15 +02:00
}
static void init_jacks ( pa_card * impl )
{
2020-09-08 16:39:47 +02:00
void * state ;
pa_alsa_path * path ;
pa_alsa_jack * jack ;
2021-01-05 10:08:31 +01:00
char buf [ 64 ] ;
2020-09-08 16:39:47 +02:00
impl - > jacks = pa_hashmap_new ( pa_idxset_trivial_hash_func , pa_idxset_trivial_compare_func ) ;
if ( impl - > use_ucm ) {
PA_LLIST_FOREACH ( jack , impl - > ucm . jacks )
if ( jack - > has_control )
pa_hashmap_put ( impl - > jacks , jack , jack ) ;
} else {
/* See if we have any jacks */
if ( impl - > profile_set - > output_paths )
PA_HASHMAP_FOREACH ( path , impl - > profile_set - > output_paths , state )
PA_LLIST_FOREACH ( jack , path - > jacks )
if ( jack - > has_control )
pa_hashmap_put ( impl - > jacks , jack , jack ) ;
if ( impl - > profile_set - > input_paths )
PA_HASHMAP_FOREACH ( path , impl - > profile_set - > input_paths , state )
PA_LLIST_FOREACH ( jack , path - > jacks )
if ( jack - > has_control )
pa_hashmap_put ( impl - > jacks , jack , jack ) ;
}
pa_log_debug ( " Found %d jacks. " , pa_hashmap_size ( impl - > jacks ) ) ;
if ( pa_hashmap_size ( impl - > jacks ) = = 0 )
return ;
PA_HASHMAP_FOREACH ( jack , impl - > jacks , state ) {
if ( ! jack - > mixer_device_name ) {
jack - > mixer_handle = pa_alsa_open_mixer ( impl - > ucm . mixers , impl - > card . index , false ) ;
if ( ! jack - > mixer_handle ) {
pa_log ( " Failed to open mixer for card %d for jack detection " , impl - > card . index ) ;
continue ;
}
} else {
jack - > mixer_handle = pa_alsa_open_mixer_by_name ( impl - > ucm . mixers , jack - > mixer_device_name , false ) ;
if ( ! jack - > mixer_handle ) {
pa_log ( " Failed to open mixer '%s' for jack detection " , jack - > mixer_device_name ) ;
continue ;
}
}
pa_alsa_mixer_use_for_poll ( impl - > ucm . mixers , jack - > mixer_handle ) ;
2021-01-05 10:08:31 +01:00
jack - > melem = pa_alsa_mixer_find_card ( jack - > mixer_handle , & jack - > alsa_id , 0 ) ;
2020-09-08 16:39:47 +02:00
if ( ! jack - > melem ) {
2021-01-05 10:08:31 +01:00
pa_alsa_mixer_id_to_string ( buf , sizeof ( buf ) , & jack - > alsa_id ) ;
pa_log_warn ( " Jack '%s' seems to have disappeared. " , buf ) ;
2020-09-08 16:39:47 +02:00
pa_alsa_jack_set_has_control ( jack , false ) ;
continue ;
}
snd_mixer_elem_set_callback ( jack - > melem , report_jack_state ) ;
snd_mixer_elem_set_callback_private ( jack - > melem , impl ) ;
report_jack_state ( jack - > melem , 0 ) ;
}
2020-05-15 19:42:15 +02:00
}
static pa_device_port * find_port_with_eld_device ( pa_card * impl , int device )
{
2020-09-08 16:39:47 +02:00
void * state ;
pa_device_port * p ;
if ( impl - > use_ucm ) {
PA_HASHMAP_FOREACH ( p , impl - > ports , state ) {
pa_alsa_ucm_port_data * data = PA_DEVICE_PORT_DATA ( p ) ;
pa_assert ( data - > eld_mixer_device_name ) ;
if ( device = = data - > eld_device )
return p ;
}
} else {
PA_HASHMAP_FOREACH ( p , impl - > ports , state ) {
pa_alsa_port_data * data = PA_DEVICE_PORT_DATA ( p ) ;
pa_assert ( data - > path ) ;
if ( device = = data - > path - > eld_device )
return p ;
}
}
return NULL ;
2020-05-15 19:42:15 +02:00
}
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
static void acp_iec958_codec_mask_to_json ( uint64_t codecs , char * buf , size_t maxsize )
{
struct spa_strbuf b ;
const struct spa_type_info * info ;
spa_strbuf_init ( & b , buf , maxsize ) ;
for ( info = spa_type_audio_iec958_codec ; info - > name ; + + info )
if ( ( codecs & ( 1ULL < < info - > type ) ) & & info - > type ! = SPA_AUDIO_IEC958_CODEC_UNKNOWN )
spa_strbuf_append ( & b , " %s \" %s \" " , ( b . pos ? " , " : " [ " ) ,
spa_type_audio_iec958_codec_to_short_name ( info - > type ) ) ;
if ( b . pos )
spa_strbuf_append ( & b , " ] " ) ;
}
void acp_iec958_codecs_to_json ( const uint32_t * codecs , size_t n_codecs , char * buf , size_t maxsize )
{
struct spa_strbuf b ;
spa_strbuf_init ( & b , buf , maxsize ) ;
spa_strbuf_append ( & b , " [ " ) ;
for ( size_t i = 0 ; i < n_codecs ; + + i )
spa_strbuf_append ( & b , " %s \" %s \" " , ( i ? " , " : " " ) ,
spa_type_audio_iec958_codec_to_short_name ( codecs [ i ] ) ) ;
spa_strbuf_append ( & b , " ] " ) ;
}
size_t acp_iec958_codecs_from_json ( const char * str , uint32_t * codecs , size_t max_codecs )
{
struct spa_json it ;
char v [ 256 ] ;
size_t n_codecs = 0 ;
if ( spa_json_begin_array_relax ( & it , str , strlen ( str ) ) < = 0 )
return 0 ;
while ( spa_json_get_string ( & it , v , sizeof ( v ) ) > 0 ) {
uint32_t type = spa_type_audio_iec958_codec_from_short_name ( v ) ;
if ( type ! = SPA_AUDIO_IEC958_CODEC_UNKNOWN )
codecs [ n_codecs + + ] = type ;
if ( n_codecs > = max_codecs )
break ;
}
return n_codecs ;
}
2020-05-15 19:42:15 +02:00
static int hdmi_eld_changed ( snd_mixer_elem_t * melem , unsigned int mask )
{
2020-09-08 16:39:47 +02:00
pa_card * impl = snd_mixer_elem_get_callback_private ( melem ) ;
2023-03-20 17:47:52 +01:00
snd_hctl_elem_t * * _elem = snd_mixer_elem_get_private ( melem ) , * elem ;
2025-10-08 16:38:51 -07:00
int device ;
const char * old_monitor_name , * old_iec958_codec_list , * old_channels , * old_position ;
2020-09-08 16:39:47 +02:00
pa_device_port * p ;
pa_hdmi_eld eld ;
bool changed = false ;
2023-03-20 17:47:52 +01:00
pa_assert ( _elem ) ;
elem = * _elem ;
device = snd_hctl_elem_get_device ( elem ) ;
2020-09-08 16:39:47 +02:00
if ( mask = = SND_CTL_EVENT_MASK_REMOVE )
return 0 ;
p = find_port_with_eld_device ( impl , device ) ;
if ( p = = NULL ) {
pa_log_error ( " Invalid device changed in ALSA: %d " , device ) ;
return 0 ;
}
if ( pa_alsa_get_hdmi_eld ( elem , & eld ) < 0 )
memset ( & eld , 0 , sizeof ( eld ) ) ;
2024-10-02 12:54:08 -04:00
// Strip trailing whitespace from monitor_name (primarily an NVidia driver bug for now)
2025-10-08 16:38:51 -07:00
for ( int i = strlen ( eld . monitor_name ) - 1 ; i > = 0 ; i - - ) {
2024-10-02 12:54:08 -04:00
if ( eld . monitor_name [ i ] = = ' \n ' | | eld . monitor_name [ i ] = = ' \r ' | | eld . monitor_name [ i ] = = ' \t ' | |
eld . monitor_name [ i ] = = ' ' )
eld . monitor_name [ i ] = 0 ;
else
break ;
}
2020-09-08 16:39:47 +02:00
old_monitor_name = pa_proplist_gets ( p - > proplist , PA_PROP_DEVICE_PRODUCT_NAME ) ;
if ( eld . monitor_name [ 0 ] = = ' \0 ' ) {
changed | = old_monitor_name ! = NULL ;
pa_proplist_unset ( p - > proplist , PA_PROP_DEVICE_PRODUCT_NAME ) ;
} else {
2021-05-18 11:43:49 +10:00
changed | = ( old_monitor_name = = NULL ) | | ( ! spa_streq ( old_monitor_name , eld . monitor_name ) ) ;
2020-09-08 16:39:47 +02:00
pa_proplist_sets ( p - > proplist , PA_PROP_DEVICE_PRODUCT_NAME , eld . monitor_name ) ;
}
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
old_iec958_codec_list = pa_proplist_gets ( p - > proplist , ACP_KEY_IEC958_CODECS_DETECTED ) ;
if ( eld . iec958_codecs = = 0 ) {
changed | = old_iec958_codec_list ! = NULL ;
pa_proplist_unset ( p - > proplist , ACP_KEY_IEC958_CODECS_DETECTED ) ;
} else {
char codecs [ 512 ] ;
acp_iec958_codec_mask_to_json ( eld . iec958_codecs , codecs , sizeof ( codecs ) ) ;
changed | = ( old_iec958_codec_list = = NULL ) | | ( ! spa_streq ( old_iec958_codec_list , codecs ) ) ;
pa_proplist_sets ( p - > proplist , ACP_KEY_IEC958_CODECS_DETECTED , codecs ) ;
}
2025-10-08 16:38:51 -07:00
old_channels = pa_proplist_gets ( p - > proplist , ACP_KEY_AUDIO_CHANNELS_DETECTED ) ;
if ( eld . lpcm_channels = = 0 ) {
changed | = old_channels ! = NULL ;
pa_proplist_unset ( p - > proplist , ACP_KEY_AUDIO_CHANNELS_DETECTED ) ;
} else {
char channels [ 4 ] ;
snprintf ( channels , sizeof ( channels ) , " %u " , eld . lpcm_channels ) ;
changed | = ( old_channels = = NULL ) | | ( ! spa_streq ( old_channels , channels ) ) ;
pa_proplist_sets ( p - > proplist , ACP_KEY_AUDIO_CHANNELS_DETECTED , channels ) ;
}
old_position = pa_proplist_gets ( p - > proplist , ACP_KEY_AUDIO_POSITION_DETECTED ) ;
if ( eld . speakers = = 0 ) {
changed | = old_position ! = NULL ;
pa_proplist_unset ( p - > proplist , ACP_KEY_AUDIO_POSITION_DETECTED ) ;
} else {
uint32_t positions [ eld . lpcm_channels ] ;
2025-10-24 17:00:11 +02:00
char position [ eld . lpcm_channels * 8 ] ;
struct spa_strbuf b ;
int i = 0 ;
2025-10-08 16:38:51 -07:00
if ( eld . speakers & 0x01 ) {
positions [ i + + ] = ACP_CHANNEL_FL ;
positions [ i + + ] = ACP_CHANNEL_FR ;
}
if ( eld . speakers & 0x02 ) {
positions [ i + + ] = ACP_CHANNEL_LFE ;
}
if ( eld . speakers & 0x04 ) {
positions [ i + + ] = ACP_CHANNEL_FC ;
}
if ( eld . speakers & 0x08 ) {
positions [ i + + ] = ACP_CHANNEL_RL ;
positions [ i + + ] = ACP_CHANNEL_RR ;
}
/* The rest are out of order in order of what channels we would prefer to use/expose first */
if ( eld . speakers & 0x40 ) {
/* Use SL/SR instead of RLC/RRC */
positions [ i + + ] = ACP_CHANNEL_SL ;
positions [ i + + ] = ACP_CHANNEL_SR ;
}
if ( eld . speakers & 0x20 ) {
positions [ i + + ] = ACP_CHANNEL_RLC ;
positions [ i + + ] = ACP_CHANNEL_RRC ;
}
if ( eld . speakers & 0x10 ) {
positions [ i + + ] = ACP_CHANNEL_RC ;
}
while ( i < eld . lpcm_channels )
positions [ i + + ] = ACP_CHANNEL_UNKNOWN ;
2025-10-24 17:00:11 +02:00
spa_strbuf_init ( & b , position , sizeof ( position ) ) ;
spa_strbuf_append ( & b , " [ " ) ;
for ( i = 0 ; i < eld . lpcm_channels ; i + + )
spa_strbuf_append ( & b , " %s%s " , i ? " , " : " " , channel_names [ positions [ i ] ] ) ;
spa_strbuf_append ( & b , " ] " ) ;
2025-10-08 16:38:51 -07:00
changed | = ( old_position = = NULL ) | | ( ! spa_streq ( old_position , position ) ) ;
pa_proplist_sets ( p - > proplist , ACP_KEY_AUDIO_POSITION_DETECTED , position ) ;
}
2020-09-22 12:40:21 +02:00
pa_proplist_as_dict ( p - > proplist , & p - > port . props ) ;
2020-09-08 16:39:47 +02:00
if ( changed & & mask ! = 0 & & impl - > events & & impl - > events - > props_changed )
2020-10-01 11:31:52 +02:00
impl - > events - > props_changed ( impl - > user_data ) ;
2020-09-08 16:39:47 +02:00
return 0 ;
2020-05-15 19:42:15 +02:00
}
static void init_eld_ctls ( pa_card * impl )
{
2020-09-08 16:39:47 +02:00
void * state ;
pa_device_port * port ;
/* The code in this function expects ports to have a pa_alsa_port_data
* struct as their data , but in UCM mode ports don ' t have any data . Hence ,
* the ELD controls can ' t currently be used in UCM mode . */
PA_HASHMAP_FOREACH ( port , impl - > ports , state ) {
snd_mixer_t * mixer_handle ;
snd_mixer_elem_t * melem ;
int device ;
if ( impl - > use_ucm ) {
pa_alsa_ucm_port_data * data = PA_DEVICE_PORT_DATA ( port ) ;
device = data - > eld_device ;
if ( device < 0 | | ! data - > eld_mixer_device_name )
continue ;
mixer_handle = pa_alsa_open_mixer_by_name ( impl - > ucm . mixers , data - > eld_mixer_device_name , true ) ;
} else {
pa_alsa_port_data * data = PA_DEVICE_PORT_DATA ( port ) ;
pa_assert ( data - > path ) ;
device = data - > path - > eld_device ;
if ( device < 0 )
continue ;
mixer_handle = pa_alsa_open_mixer ( impl - > ucm . mixers , impl - > card . index , true ) ;
}
if ( ! mixer_handle )
continue ;
melem = pa_alsa_mixer_find_pcm ( mixer_handle , " ELD " , device ) ;
if ( melem ) {
pa_alsa_mixer_use_for_poll ( impl - > ucm . mixers , mixer_handle ) ;
snd_mixer_elem_set_callback ( melem , hdmi_eld_changed ) ;
snd_mixer_elem_set_callback_private ( melem , impl ) ;
hdmi_eld_changed ( melem , 0 ) ;
pa_log_info ( " ELD device found for port %s (%d). " , port - > port . name , device ) ;
}
else
pa_log_debug ( " No ELD device found for port %s (%d). " , port - > port . name , device ) ;
}
2020-05-15 19:42:15 +02:00
}
2020-09-08 16:39:47 +02:00
uint32_t acp_card_find_best_profile_index ( struct acp_card * card , const char * name )
2020-05-15 19:42:15 +02:00
{
uint32_t i ;
2020-10-01 13:15:15 +02:00
uint32_t best , best2 , off ;
2020-09-08 16:39:47 +02:00
struct acp_card_profile * * profiles = card - > profiles ;
2020-05-15 19:42:15 +02:00
2020-10-01 13:15:15 +02:00
best = best2 = ACP_INVALID_INDEX ;
2020-09-28 11:39:09 +02:00
off = 0 ;
2020-09-09 14:08:15 +02:00
2020-09-08 16:39:47 +02:00
for ( i = 0 ; i < card - > n_profiles ; i + + ) {
2020-05-15 19:42:15 +02:00
struct acp_card_profile * p = profiles [ i ] ;
2023-11-30 17:40:48 +01:00
if ( SPA_FLAG_IS_SET ( p - > flags , ACP_PROFILE_HIDDEN ) )
continue ;
2020-09-08 16:39:47 +02:00
if ( name ) {
2021-05-18 11:36:13 +10:00
if ( spa_streq ( name , p - > name ) )
2020-05-15 19:42:15 +02:00
best = i ;
2021-01-27 13:04:24 +01:00
} else if ( p - > flags & ACP_PROFILE_OFF ) {
2020-09-28 11:39:09 +02:00
off = i ;
} else if ( p - > available = = ACP_AVAILABLE_YES ) {
2020-09-08 16:39:47 +02:00
if ( best = = ACP_INVALID_INDEX | | p - > priority > profiles [ best ] - > priority )
2020-05-15 19:42:15 +02:00
best = i ;
2020-09-09 14:08:15 +02:00
} else if ( p - > available ! = ACP_AVAILABLE_NO ) {
if ( best2 = = ACP_INVALID_INDEX | | p - > priority > profiles [ best2 ] - > priority )
best2 = i ;
2020-05-15 19:42:15 +02:00
}
}
2020-09-08 16:39:47 +02:00
if ( best = = ACP_INVALID_INDEX )
2020-09-09 14:08:15 +02:00
best = best2 ;
2020-09-08 16:39:47 +02:00
if ( best = = ACP_INVALID_INDEX )
2020-09-28 11:39:09 +02:00
best = off ;
2020-09-08 16:39:47 +02:00
return best ;
2020-05-15 19:42:15 +02:00
}
2020-09-08 16:39:47 +02:00
static void find_mixer ( pa_card * impl , pa_alsa_device * dev , const char * element , bool ignore_dB )
{
2025-06-14 13:54:23 +03:00
const char * mdev = NULL ;
2020-09-08 16:39:47 +02:00
pa_alsa_mapping * mapping = dev - > mapping ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( ! mapping & & ! element )
return ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( ! element & & mapping & & pa_alsa_path_set_is_empty ( dev - > mixer_path_set ) )
return ;
2020-05-15 19:42:15 +02:00
2025-06-14 13:54:23 +03:00
if ( mapping )
mdev = pa_proplist_gets ( mapping - > proplist , " alsa.mixer_device " ) ;
2020-09-08 16:39:47 +02:00
if ( mdev ) {
dev - > mixer_handle = pa_alsa_open_mixer_by_name ( impl - > ucm . mixers , mdev , true ) ;
} else {
dev - > mixer_handle = pa_alsa_open_mixer ( impl - > ucm . mixers , impl - > card . index , true ) ;
}
if ( ! dev - > mixer_handle ) {
pa_log_info ( " Failed to find a working mixer device. " ) ;
return ;
}
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( element ) {
if ( ! ( dev - > mixer_path = pa_alsa_path_synthesize ( element , dev - > direction ) ) )
goto fail ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( pa_alsa_path_probe ( dev - > mixer_path , NULL , dev - > mixer_handle , ignore_dB ) < 0 )
goto fail ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
pa_log_debug ( " Probed mixer path %s: " , dev - > mixer_path - > name ) ;
pa_alsa_path_dump ( dev - > mixer_path ) ;
}
return ;
2020-05-15 19:42:15 +02:00
fail :
2020-09-08 16:39:47 +02:00
if ( dev - > mixer_path ) {
pa_alsa_path_free ( dev - > mixer_path ) ;
dev - > mixer_path = NULL ;
}
dev - > mixer_handle = NULL ;
2020-05-15 19:42:15 +02:00
}
static int mixer_callback ( snd_mixer_elem_t * elem , unsigned int mask )
{
2020-09-08 16:39:47 +02:00
pa_alsa_device * dev = snd_mixer_elem_get_callback_private ( elem ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( mask = = SND_CTL_EVENT_MASK_REMOVE )
return 0 ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
pa_log_info ( " %p mixer changed %d " , dev , mask ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( mask & SND_CTL_EVENT_MASK_VALUE ) {
if ( dev - > read_volume )
dev - > read_volume ( dev ) ;
if ( dev - > read_mute )
dev - > read_mute ( dev ) ;
}
return 0 ;
2020-05-15 19:42:15 +02:00
}
static int read_volume ( pa_alsa_device * dev )
{
pa_card * impl = dev - > card ;
pa_cvolume r ;
uint32_t i ;
2020-09-08 16:39:47 +02:00
int res ;
2020-05-15 19:42:15 +02:00
2023-11-28 11:03:22 +03:00
if ( dev - > ucm_context ) {
if ( ! dev - > active_port )
return 0 ;
pa_alsa_ucm_port_data * data = PA_DEVICE_PORT_DATA ( dev - > active_port ) ;
if ( pa_alsa_ucm_port_device_status ( data ) < = 0 )
return 0 ;
}
2022-08-04 09:05:30 +02:00
if ( ! dev - > mixer_handle )
return 0 ;
2025-03-28 14:18:10 +00:00
if ( dev - > mixer_path - > has_volume_mute & & dev - > muted ) {
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar ( & r , & dev - > hardware_volume , dev - > base_volume ) ;
pa_log_debug ( " Reading cached volume only. " ) ;
} else {
if ( ( res = pa_alsa_path_get_volume ( dev - > mixer_path , dev - > mixer_handle ,
& dev - > mapping - > channel_map , & r ) ) < 0 )
return res ;
}
2020-05-15 19:42:15 +02:00
/* Shift down by the base volume, so that 0dB becomes maximum volume */
pa_sw_cvolume_multiply_scalar ( & r , & r , dev - > base_volume ) ;
2021-08-09 14:13:51 +02:00
if ( pa_cvolume_equal ( & dev - > hardware_volume , & r ) )
2020-05-15 19:42:15 +02:00
return 0 ;
2021-08-09 14:13:51 +02:00
dev - > real_volume = dev - > hardware_volume = r ;
2021-05-10 10:50:42 +02:00
pa_log_info ( " New hardware volume: min:%d max:%d " ,
pa_cvolume_min ( & r ) , pa_cvolume_max ( & r ) ) ;
2020-05-15 19:42:15 +02:00
for ( i = 0 ; i < r . channels ; i + + )
pa_log_debug ( " %d: %d " , i , r . values [ i ] ) ;
2021-05-10 10:50:42 +02:00
pa_cvolume_reset ( & dev - > soft_volume , r . channels ) ;
2020-05-15 19:42:15 +02:00
if ( impl - > events & & impl - > events - > volume_changed )
impl - > events - > volume_changed ( impl - > user_data , & dev - > device ) ;
return 0 ;
}
static void set_volume ( pa_alsa_device * dev , const pa_cvolume * v )
{
pa_cvolume r ;
2025-03-28 14:18:10 +00:00
bool write_to_hw ;
2020-05-15 19:42:15 +02:00
2023-12-02 13:13:55 +02:00
if ( v ! = & dev - > real_volume )
dev - > real_volume = * v ;
2020-05-15 19:42:15 +02:00
2023-11-28 11:03:22 +03:00
if ( dev - > ucm_context ) {
if ( ! dev - > active_port )
return ;
pa_alsa_ucm_port_data * data = PA_DEVICE_PORT_DATA ( dev - > active_port ) ;
if ( pa_alsa_ucm_port_device_status ( data ) < = 0 )
return ;
}
2022-08-04 09:05:30 +02:00
if ( ! dev - > mixer_handle )
return ;
2020-05-15 19:42:15 +02:00
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar ( & r , & dev - > real_volume , dev - > base_volume ) ;
2025-03-28 14:18:10 +00:00
write_to_hw = ! ( dev - > mixer_path - > has_volume_mute & & dev - > muted ) ;
2020-05-15 19:42:15 +02:00
if ( pa_alsa_path_set_volume ( dev - > mixer_path , dev - > mixer_handle , & dev - > mapping - > channel_map ,
2025-03-28 14:18:10 +00:00
& r , false , write_to_hw ) < 0 )
2020-05-15 19:42:15 +02:00
return ;
2020-07-06 17:22:42 +02:00
/* Shift down by the base volume, so that 0dB becomes maximum volume */
pa_sw_cvolume_multiply_scalar ( & r , & r , dev - > base_volume ) ;
dev - > hardware_volume = r ;
if ( dev - > mixer_path - > has_dB ) {
pa_cvolume new_soft_volume ;
bool accurate_enough ;
/* Match exactly what the user requested by software */
pa_sw_cvolume_divide ( & new_soft_volume , & dev - > real_volume , & dev - > hardware_volume ) ;
/* If the adjustment to do in software is only minimal we
* can skip it . That saves us CPU at the expense of a bit of
* accuracy */
accurate_enough =
( pa_cvolume_min ( & new_soft_volume ) > = ( PA_VOLUME_NORM - VOLUME_ACCURACY ) ) & &
( pa_cvolume_max ( & new_soft_volume ) < = ( PA_VOLUME_NORM + VOLUME_ACCURACY ) ) ;
pa_log_debug ( " Requested volume: %d " , pa_cvolume_max ( & dev - > real_volume ) ) ;
pa_log_debug ( " Got hardware volume: %d " , pa_cvolume_max ( & dev - > hardware_volume ) ) ;
pa_log_debug ( " Calculated software volume: %d (accurate-enough=%s) " ,
pa_cvolume_max ( & new_soft_volume ) ,
pa_yes_no ( accurate_enough ) ) ;
2020-09-28 12:40:16 +02:00
if ( accurate_enough )
pa_cvolume_reset ( & new_soft_volume , new_soft_volume . channels ) ;
2021-03-21 16:19:57 +01:00
dev - > soft_volume = new_soft_volume ;
2020-07-06 17:22:42 +02:00
} else {
pa_log_debug ( " Wrote hardware volume: %d " , pa_cvolume_max ( & r ) ) ;
/* We can't match exactly what the user requested, hence let's
* at least tell the user about it */
dev - > real_volume = r ;
}
2020-05-15 19:42:15 +02:00
}
static int read_mute ( pa_alsa_device * dev )
{
pa_card * impl = dev - > card ;
bool mute ;
2020-09-08 16:39:47 +02:00
int res ;
2020-05-15 19:42:15 +02:00
2023-11-28 11:03:22 +03:00
if ( dev - > ucm_context ) {
if ( ! dev - > active_port )
return 0 ;
pa_alsa_ucm_port_data * data = PA_DEVICE_PORT_DATA ( dev - > active_port ) ;
if ( pa_alsa_ucm_port_device_status ( data ) < = 0 )
return 0 ;
}
2022-08-04 09:05:30 +02:00
if ( ! dev - > mixer_handle )
return 0 ;
2025-03-28 14:18:10 +00:00
if ( dev - > mixer_path - > has_volume_mute ) {
pa_cvolume mute_vol ;
pa_cvolume r ;
pa_cvolume_mute ( & mute_vol , dev - > mapping - > channel_map . channels ) ;
if ( ( res = pa_alsa_path_get_volume ( dev - > mixer_path , dev - > mixer_handle , & dev - > mapping - > channel_map , & r ) ) < 0 )
return res ;
mute = pa_cvolume_equal ( & mute_vol , & r ) ;
} else {
if ( ( res = pa_alsa_path_get_mute ( dev - > mixer_path , dev - > mixer_handle , & mute ) ) < 0 )
return res ;
}
2020-05-15 19:42:15 +02:00
if ( mute = = dev - > muted )
return 0 ;
dev - > muted = mute ;
2020-07-08 16:16:43 +02:00
pa_log_info ( " New hardware muted: %d " , mute ) ;
2020-05-15 19:42:15 +02:00
if ( impl - > events & & impl - > events - > mute_changed )
impl - > events - > mute_changed ( impl - > user_data , & dev - > device ) ;
return 0 ;
}
static void set_mute ( pa_alsa_device * dev , bool mute )
{
dev - > muted = mute ;
2022-08-04 09:05:30 +02:00
2023-11-28 11:03:22 +03:00
if ( dev - > ucm_context ) {
if ( ! dev - > active_port )
return ;
pa_alsa_ucm_port_data * data = PA_DEVICE_PORT_DATA ( dev - > active_port ) ;
if ( pa_alsa_ucm_port_device_status ( data ) < = 0 )
return ;
}
2022-08-04 09:05:30 +02:00
if ( ! dev - > mixer_handle )
return ;
2025-03-28 14:18:10 +00:00
if ( dev - > mixer_path - > has_volume_mute ) {
pa_cvolume r ;
if ( mute ) {
pa_cvolume_mute ( & r , dev - > mapping - > channel_map . channels ) ;
pa_alsa_path_set_volume ( dev - > mixer_path , dev - > mixer_handle , & dev - > mapping - > channel_map ,
& r , false , true ) ;
} else {
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar ( & r , & dev - > real_volume , dev - > base_volume ) ;
pa_log_debug ( " Restoring volume: %d " , pa_cvolume_max ( & dev - > real_volume ) ) ;
if ( pa_alsa_path_set_volume ( dev - > mixer_path , dev - > mixer_handle , & dev - > mapping - > channel_map ,
& r , false , true ) < 0 )
pa_log_error ( " Unable to restore volume %d during unmute " ,
pa_cvolume_max ( & dev - > real_volume ) ) ;
}
} else {
pa_alsa_path_set_mute ( dev - > mixer_path , dev - > mixer_handle , mute ) ;
}
2020-05-15 19:42:15 +02:00
}
2020-12-01 10:43:39 +01:00
static void mixer_volume_init ( pa_card * impl , pa_alsa_device * dev )
2020-09-08 16:39:47 +02:00
{
pa_assert ( dev ) ;
2020-12-01 10:43:39 +01:00
if ( impl - > soft_mixer | | ! dev - > mixer_path | | ! dev - > mixer_path - > has_volume ) {
2020-09-08 16:39:47 +02:00
dev - > read_volume = NULL ;
dev - > set_volume = NULL ;
pa_log_info ( " Driver does not support hardware volume control, "
" falling back to software volume control. " ) ;
dev - > base_volume = PA_VOLUME_NORM ;
dev - > n_volume_steps = PA_VOLUME_NORM + 1 ;
dev - > device . flags & = ~ ACP_DEVICE_HW_VOLUME ;
} else {
dev - > read_volume = read_volume ;
dev - > set_volume = set_volume ;
dev - > device . flags | = ACP_DEVICE_HW_VOLUME ;
2020-05-15 19:42:15 +02:00
#if 0
2020-09-08 16:39:47 +02:00
if ( u - > mixer_path - > has_dB & & u - > deferred_volume ) {
pa_sink_set_write_volume_callback ( u - > sink , sink_write_volume_cb ) ;
pa_log_info ( " Successfully enabled deferred volume. " ) ;
} else
pa_sink_set_write_volume_callback ( u - > sink , NULL ) ;
2020-05-15 19:42:15 +02:00
# endif
2020-09-08 16:39:47 +02:00
if ( dev - > mixer_path - > has_dB ) {
dev - > decibel_volume = true ;
pa_log_info ( " Hardware volume ranges from %0.2f dB to %0.2f dB. " ,
dev - > mixer_path - > min_dB , dev - > mixer_path - > max_dB ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
dev - > base_volume = pa_sw_volume_from_dB ( - dev - > mixer_path - > max_dB ) ;
dev - > n_volume_steps = PA_VOLUME_NORM + 1 ;
2020-05-15 19:42:15 +02:00
2025-03-28 14:18:10 +00:00
/* If minimum volume is set to -99999 dB, then volume control supports
* mute */
if ( dev - > mixer_path - > min_dB = = - 99999.99 & & ! dev - > mixer_path - > has_mute )
dev - > mixer_path - > has_volume_mute = true ;
2020-09-08 16:39:47 +02:00
pa_log_info ( " Fixing base volume to %0.2f dB " , pa_sw_volume_to_dB ( dev - > base_volume ) ) ;
} else {
dev - > decibel_volume = false ;
pa_log_info ( " Hardware volume ranges from %li to %li. " ,
dev - > mixer_path - > min_volume , dev - > mixer_path - > max_volume ) ;
dev - > base_volume = PA_VOLUME_NORM ;
dev - > n_volume_steps = dev - > mixer_path - > max_volume - dev - > mixer_path - > min_volume + 1 ;
}
pa_log_info ( " Using hardware volume control. Hardware dB scale %s. " ,
dev - > mixer_path - > has_dB ? " supported " : " not supported " ) ;
}
2024-06-18 12:17:56 +02:00
dev - > device . base_volume = ( float ) pa_sw_volume_to_linear ( dev - > base_volume ) ;
2020-09-08 16:39:47 +02:00
dev - > device . volume_step = 1.0f / dev - > n_volume_steps ;
2025-03-28 14:18:10 +00:00
if ( impl - > soft_mixer | | ! dev - > mixer_path | |
( ! dev - > mixer_path - > has_mute & & ! dev - > mixer_path - > has_volume_mute ) ) {
2020-09-08 16:39:47 +02:00
dev - > read_mute = NULL ;
dev - > set_mute = NULL ;
pa_log_info ( " Driver does not support hardware mute control, falling back to software mute control. " ) ;
dev - > device . flags & = ~ ACP_DEVICE_HW_MUTE ;
} else {
dev - > read_mute = read_mute ;
dev - > set_mute = set_mute ;
2025-03-28 14:18:10 +00:00
pa_log_info ( " Using hardware %smute control. " ,
dev - > mixer_path - > has_volume_mute ? " volume- " : " " ) ;
2020-09-08 16:39:47 +02:00
dev - > device . flags | = ACP_DEVICE_HW_MUTE ;
}
2020-05-15 19:42:15 +02:00
}
2020-09-08 16:39:47 +02:00
static int setup_mixer ( pa_card * impl , pa_alsa_device * dev , bool ignore_dB )
2020-05-15 19:42:15 +02:00
{
2020-09-08 16:39:47 +02:00
int res ;
bool need_mixer_callback = false ;
/* This code is before the u->mixer_handle check, because if the UCM
* configuration doesn ' t specify volume or mute controls , u - > mixer_handle
* will be NULL , but the UCM device enable sequence will still need to be
* executed . */
if ( dev - > active_port & & dev - > ucm_context ) {
2023-11-27 23:49:27 +03:00
if ( ( res = pa_alsa_ucm_set_port ( dev - > ucm_context , dev - > active_port ) ) < 0 )
2020-09-08 16:39:47 +02:00
return res ;
2020-05-15 19:42:15 +02:00
}
2020-09-08 16:39:47 +02:00
if ( ! dev - > mixer_handle )
return 0 ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( dev - > active_port ) {
if ( ! impl - > use_ucm ) {
pa_alsa_port_data * data ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
/* We have a list of supported paths, so let's activate the
* one that has been chosen as active */
data = PA_DEVICE_PORT_DATA ( dev - > active_port ) ;
dev - > mixer_path = data - > path ;
2020-05-15 19:42:15 +02:00
2024-09-24 13:14:17 +02:00
if ( ! impl - > disable_mixer_path )
2024-09-23 15:34:08 +02:00
pa_alsa_path_select ( data - > path , data - > setting , dev - > mixer_handle , dev - > muted ) ;
2020-09-08 16:39:47 +02:00
} else {
pa_alsa_ucm_port_data * data ;
data = PA_DEVICE_PORT_DATA ( dev - > active_port ) ;
/* Now activate volume controls, if any */
if ( data - > path ) {
dev - > mixer_path = data - > path ;
2024-09-24 13:14:17 +02:00
if ( ! impl - > disable_mixer_path )
2024-09-23 15:34:08 +02:00
pa_alsa_path_select ( dev - > mixer_path , NULL , dev - > mixer_handle , dev - > muted ) ;
2020-09-08 16:39:47 +02:00
}
}
} else {
if ( ! dev - > mixer_path & & dev - > mixer_path_set )
dev - > mixer_path = pa_hashmap_first ( dev - > mixer_path_set - > paths ) ;
2020-05-15 19:42:15 +02:00
2020-09-08 16:39:47 +02:00
if ( dev - > mixer_path ) {
/* Hmm, we have only a single path, then let's activate it */
2024-09-24 13:14:17 +02:00
if ( ! impl - > disable_mixer_path )
2024-09-23 15:34:08 +02:00
pa_alsa_path_select ( dev - > mixer_path , dev - > mixer_path - > settings ,
dev - > mixer_handle , dev - > muted ) ;
2020-09-08 16:39:47 +02:00
} else
return 0 ;
2020-05-15 19:42:15 +02:00
}
2020-09-08 16:39:47 +02:00
2020-12-01 10:43:39 +01:00
mixer_volume_init ( impl , dev ) ;
2020-09-08 16:39:47 +02:00
/* Will we need to register callbacks? */
if ( dev - > mixer_path_set & & dev - > mixer_path_set - > paths ) {
pa_alsa_path * p ;
void * state ;
PA_HASHMAP_FOREACH ( p , dev - > mixer_path_set - > paths , state ) {
if ( p - > has_volume | | p - > has_mute )
need_mixer_callback = true ;
}
}
else if ( dev - > mixer_path )
need_mixer_callback = dev - > mixer_path - > has_volume | | dev - > mixer_path - > has_mute ;
2020-12-01 10:43:39 +01:00
if ( ! impl - > soft_mixer & & need_mixer_callback ) {
2020-09-08 16:39:47 +02:00
pa_alsa_mixer_use_for_poll ( impl - > ucm . mixers , dev - > mixer_handle ) ;
if ( dev - > mixer_path_set )
pa_alsa_path_set_set_callback ( dev - > mixer_path_set , dev - > mixer_handle , mixer_callback , dev ) ;
else
pa_alsa_path_set_callback ( dev - > mixer_path , dev - > mixer_handle , mixer_callback , dev ) ;
}
return 0 ;
2020-05-15 19:42:15 +02:00
}
2020-09-08 16:39:47 +02:00
static int device_disable ( pa_card * impl , pa_alsa_mapping * mapping , pa_alsa_device * dev )
{
dev - > device . flags & = ~ ACP_DEVICE_ACTIVE ;
if ( dev - > active_port ) {
dev - > active_port - > port . flags & = ~ ACP_PORT_ACTIVE ;
dev - > active_port = NULL ;
}
return 0 ;
}
2020-05-15 19:42:15 +02:00
static int device_enable ( pa_card * impl , pa_alsa_mapping * mapping , pa_alsa_device * dev )
{
const char * mod_name ;
2020-12-17 17:52:21 +01:00
uint32_t i , port_index ;
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
pa_device_port * p ;
void * state = NULL ;
2020-09-08 16:39:47 +02:00
int res ;
2020-05-15 19:42:15 +02:00
if ( impl - > use_ucm & &
( mod_name = pa_proplist_gets ( mapping - > proplist , PA_ALSA_PROP_UCM_MODIFIER ) ) ) {
if ( snd_use_case_set ( impl - > ucm . ucm_mgr , " _enamod " , mod_name ) < 0 )
pa_log ( " Failed to enable ucm modifier %s " , mod_name ) ;
else
pa_log_debug ( " Enabled ucm modifier %s " , mod_name ) ;
2020-09-08 16:39:47 +02:00
}
2020-05-15 19:42:15 +02:00
pa_log_info ( " Device: %s mapping '%s' (%s). " , dev - > device . description ,
mapping - > description , mapping - > name ) ;
dev - > device . flags | = ACP_DEVICE_ACTIVE ;
2022-12-06 13:39:45 +01:00
find_mixer ( impl , dev , NULL , impl - > ignore_dB ) ;
2020-05-15 19:42:15 +02:00
2020-12-17 17:52:21 +01:00
/* Synchronize priority values, as it may have changed when setting the profile */
for ( i = 0 ; i < impl - > card . n_ports ; i + + ) {
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
p = ( pa_device_port * ) impl - > card . ports [ i ] ;
2020-12-17 17:52:21 +01:00
p - > port . priority = p - > priority ;
}
2021-01-29 16:59:17 +01:00
if ( impl - > auto_port )
port_index = acp_device_find_best_port_index ( & dev - > device , NULL ) ;
else
port_index = ACP_INVALID_INDEX ;
2020-10-08 18:04:34 +02:00
if ( port_index = = ACP_INVALID_INDEX )
dev - > active_port = NULL ;
else
dev - > active_port = ( pa_device_port * ) impl - > card . ports [ port_index ] ;
2021-01-29 16:59:17 +01:00
2020-05-15 19:42:15 +02:00
if ( dev - > active_port )
dev - > active_port - > port . flags | = ACP_PORT_ACTIVE ;
2025-10-09 20:24:14 -07:00
if ( impl - > use_eld_channels ) {
while ( ( p = pa_hashmap_iterate ( dev - > ports , & state , NULL ) ) ) {
const char * channels = pa_proplist_gets ( p - > proplist , ACP_KEY_AUDIO_CHANNELS_DETECTED ) ;
const char * positions = pa_proplist_gets ( p - > proplist , ACP_KEY_AUDIO_POSITION_DETECTED ) ;
if ( channels & & positions ) {
const char * position , * split_state = NULL ;
size_t i = 0 , n ;
dev - > device . format . channels = atoi ( channels ) ;
free ( dev - > device . format . map ) ;
dev - > device . format . map = calloc ( dev - > device . format . channels , sizeof ( uint32_t ) ) ;
while ( ( position = pa_split_in_place ( positions , " , " , & n , & split_state ) ) ! = NULL & &
i < dev - > device . format . channels ) {
dev - > device . format . map [ i + + ] = acp_channel_from_str ( position , n ) ;
}
break ;
}
}
}
2022-12-06 13:39:45 +01:00
if ( ( res = setup_mixer ( impl , dev , impl - > ignore_dB ) ) < 0 )
2020-09-08 16:39:47 +02:00
return res ;
2020-05-15 19:42:15 +02:00
2020-07-08 16:16:43 +02:00
if ( dev - > read_volume )
dev - > read_volume ( dev ) ;
2021-06-01 11:47:59 +02:00
else {
pa_cvolume_reset ( & dev - > real_volume , dev - > device . format . channels ) ;
pa_cvolume_reset ( & dev - > soft_volume , dev - > device . format . channels ) ;
}
2020-07-08 16:16:43 +02:00
if ( dev - > read_mute )
dev - > read_mute ( dev ) ;
2021-06-01 11:47:59 +02:00
else
dev - > muted = false ;
2025-10-09 20:24:14 -07:00
state = NULL ;
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
while ( ( p = pa_hashmap_iterate ( dev - > ports , & state , NULL ) ) ) {
2025-10-09 20:24:14 -07:00
const char * codecs = pa_proplist_gets ( p - > proplist , ACP_KEY_IEC958_CODECS_DETECTED ) ;
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
if ( codecs ) {
dev - > device . n_codecs = acp_iec958_codecs_from_json ( codecs , dev - > device . codecs ,
ACP_N_ELEMENTS ( dev - > device . codecs ) ) ;
}
2025-10-09 20:24:14 -07:00
if ( codecs )
break ;
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
}
2020-05-15 19:42:15 +02:00
return 0 ;
}
2021-02-22 16:42:29 +01:00
int acp_card_set_profile ( struct acp_card * card , uint32_t new_index , uint32_t flags )
2020-05-15 19:42:15 +02:00
{
pa_card * impl = ( pa_card * ) card ;
pa_alsa_mapping * am ;
uint32_t old_index = impl - > card . active_profile_index ;
struct acp_card_profile * * profiles = card - > profiles ;
pa_alsa_profile * op , * np ;
uint32_t idx ;
2020-08-18 16:52:09 +02:00
int res ;
2020-05-15 19:42:15 +02:00
if ( new_index > = card - > n_profiles )
return - EINVAL ;
np = ( pa_alsa_profile * ) profiles [ new_index ] ;
2023-11-30 17:40:48 +01:00
if ( SPA_FLAG_IS_SET ( np - > profile . flags , ACP_PROFILE_HIDDEN ) )
return - EINVAL ;
2020-05-15 19:42:15 +02:00
2023-11-30 17:40:48 +01:00
op = old_index ! = ACP_INVALID_INDEX ? ( pa_alsa_profile * ) profiles [ old_index ] : NULL ;
2020-10-01 13:17:09 +02:00
if ( op = = np )
return 0 ;
pa_log_info ( " activate profile: %s (%d) " , np - > profile . name , new_index ) ;
2020-05-15 19:42:15 +02:00
if ( op & & op - > output_mappings ) {
PA_IDXSET_FOREACH ( am , op - > output_mappings , idx ) {
if ( np - > output_mappings & &
pa_idxset_get_by_data ( np - > output_mappings , am , NULL ) )
continue ;
device_disable ( impl , am , & am - > output ) ;
}
}
if ( op & & op - > input_mappings ) {
PA_IDXSET_FOREACH ( am , op - > input_mappings , idx ) {
if ( np - > input_mappings & &
pa_idxset_get_by_data ( np - > input_mappings , am , NULL ) )
continue ;
device_disable ( impl , am , & am - > input ) ;
}
}
/* if UCM is available for this card then update the verb */
2023-11-28 13:40:04 +03:00
if ( impl - > use_ucm ) {
if ( np - > profile . flags & ACP_PROFILE_OFF ) {
if ( ( res = pa_alsa_ucm_set_profile ( & impl - > ucm , impl , NULL , op ) ) < 0 )
return res ;
} else if ( np - > profile . flags & ACP_PROFILE_PRO ) {
const char * verb = find_best_verb ( impl ) ;
if ( ( res = pa_alsa_ucm_set_profile ( & impl - > ucm , impl , NULL , op ) ) < 0 )
return res ;
if ( ( res = snd_use_case_set ( impl - > ucm . ucm_mgr , " _verb " , verb ) ) < 0 ) {
pa_log_error ( " error setting verb: %s " , snd_strerror ( res ) ) ;
return res ;
}
} else if ( ( res = pa_alsa_ucm_set_profile ( & impl - > ucm , impl , np , op ) ) < 0 ) {
2020-08-18 16:52:09 +02:00
return res ;
2020-05-15 19:42:15 +02:00
}
}
if ( np - > output_mappings ) {
PA_IDXSET_FOREACH ( am , np - > output_mappings , idx ) {
2023-01-03 13:16:06 +01:00
if ( impl - > use_ucm ) {
2020-12-17 17:52:21 +01:00
/* Update ports priorities */
2023-11-27 23:52:45 +03:00
if ( am - > ucm_context . ucm_device ) {
2023-11-27 23:30:36 +03:00
pa_alsa_ucm_add_port ( am - > output . ports , & am - > ucm_context ,
2023-01-03 13:16:06 +01:00
true , impl - > ports , np , NULL ) ;
}
}
2020-05-15 19:42:15 +02:00
device_enable ( impl , am , & am - > output ) ;
}
2020-09-08 16:39:47 +02:00
}
2020-05-15 19:42:15 +02:00
if ( np - > input_mappings ) {
PA_IDXSET_FOREACH ( am , np - > input_mappings , idx ) {
2023-01-03 13:16:06 +01:00
if ( impl - > use_ucm ) {
2020-12-17 17:52:21 +01:00
/* Update ports priorities */
2023-11-27 23:52:45 +03:00
if ( am - > ucm_context . ucm_device ) {
2023-11-27 23:30:36 +03:00
pa_alsa_ucm_add_port ( am - > input . ports , & am - > ucm_context ,
2023-01-03 13:16:06 +01:00
false , impl - > ports , np , NULL ) ;
}
}
2020-05-15 19:42:15 +02:00
device_enable ( impl , am , & am - > input ) ;
}
2020-09-08 16:39:47 +02:00
}
2020-05-15 19:42:15 +02:00
if ( op )
2021-02-22 16:42:29 +01:00
op - > profile . flags & = ~ ( ACP_PROFILE_ACTIVE | ACP_PROFILE_SAVE ) ;
np - > profile . flags | = ACP_PROFILE_ACTIVE | flags ;
2020-05-15 19:42:15 +02:00
impl - > card . active_profile_index = new_index ;
if ( impl - > events & & impl - > events - > profile_changed )
impl - > events - > profile_changed ( impl - > user_data , old_index ,
new_index ) ;
return 0 ;
}
2020-12-17 11:03:31 +01:00
static void prune_singleton_availability_groups ( pa_hashmap * ports ) {
pa_device_port * p ;
pa_hashmap * group_counts ;
void * state , * count ;
const char * group ;
/* Collect groups and erase those that don't have more than 1 path */
group_counts = pa_hashmap_new ( pa_idxset_string_hash_func , pa_idxset_string_compare_func ) ;
PA_HASHMAP_FOREACH ( p , ports , state ) {
if ( p - > availability_group ) {
count = pa_hashmap_get ( group_counts , p - > availability_group ) ;
pa_hashmap_remove ( group_counts , p - > availability_group ) ;
pa_hashmap_put ( group_counts , p - > availability_group , PA_UINT_TO_PTR ( PA_PTR_TO_UINT ( count ) + 1 ) ) ;
}
}
/* Now we have an availability_group -> count map, let's drop all groups
* that have only one member */
PA_HASHMAP_FOREACH_KV ( group , count , group_counts , state ) {
if ( count = = PA_UINT_TO_PTR ( 1 ) )
pa_hashmap_remove ( group_counts , group ) ;
}
PA_HASHMAP_FOREACH ( p , ports , state ) {
if ( p - > availability_group & & ! pa_hashmap_get ( group_counts , p - > availability_group ) ) {
pa_log_debug ( " Pruned singleton availability group %s from port %s " , p - > availability_group , p - > name ) ;
pa_xfree ( p - > availability_group ) ;
p - > availability_group = NULL ;
}
}
pa_hashmap_free ( group_counts ) ;
}
2020-05-15 19:42:15 +02:00
struct acp_card * acp_card_new ( uint32_t index , const struct acp_dict * props )
{
pa_card * impl ;
struct acp_card * card ;
const char * s , * profile_set = NULL , * profile = NULL ;
char device_id [ 16 ] ;
2020-09-08 16:39:47 +02:00
uint32_t profile_index ;
2020-09-30 12:56:05 +02:00
int res ;
2020-05-15 19:42:15 +02:00
impl = calloc ( 1 , sizeof ( * impl ) ) ;
if ( impl = = NULL )
return NULL ;
pa_alsa_refcnt_inc ( ) ;
snprintf ( device_id , sizeof ( device_id ) , " %d " , index ) ;
impl - > proplist = pa_proplist_new_dict ( props ) ;
card = & impl - > card ;
card - > index = index ;
2020-09-08 16:39:47 +02:00
card - > active_profile_index = ACP_INVALID_INDEX ;
2020-05-15 19:42:15 +02:00
impl - > use_ucm = true ;
2021-01-27 13:43:12 +01:00
impl - > auto_profile = true ;
impl - > auto_port = true ;
2022-12-06 13:39:45 +01:00
impl - > ignore_dB = false ;
2022-12-16 12:57:17 +01:00
impl - > rate = DEFAULT_RATE ;
2025-04-04 15:34:39 +02:00
impl - > pro_channels = DEFAULT_CHANNELS ;
2020-05-15 19:42:15 +02:00
if ( props ) {
2020-08-03 17:08:46 +02:00
if ( ( s = acp_dict_lookup ( props , " api.alsa.use-ucm " ) ) ! = NULL )
2021-05-18 15:18:14 +10:00
impl - > use_ucm = spa_atob ( s ) ;
2020-12-01 10:43:39 +01:00
if ( ( s = acp_dict_lookup ( props , " api.alsa.soft-mixer " ) ) ! = NULL )
2021-05-18 15:18:14 +10:00
impl - > soft_mixer = spa_atob ( s ) ;
2024-09-24 13:14:17 +02:00
if ( ( s = acp_dict_lookup ( props , " api.alsa.disable-mixer-path " ) ) ! = NULL )
impl - > disable_mixer_path = spa_atob ( s ) ;
2020-08-03 17:08:46 +02:00
if ( ( s = acp_dict_lookup ( props , " api.alsa.ignore-dB " ) ) ! = NULL )
2022-12-06 13:39:45 +01:00
impl - > ignore_dB = spa_atob ( s ) ;
2020-08-03 17:08:46 +02:00
if ( ( s = acp_dict_lookup ( props , " device.profile-set " ) ) ! = NULL )
profile_set = s ;
if ( ( s = acp_dict_lookup ( props , " device.profile " ) ) ! = NULL )
2020-05-15 19:42:15 +02:00
profile = s ;
2021-01-27 13:43:12 +01:00
if ( ( s = acp_dict_lookup ( props , " api.acp.auto-profile " ) ) ! = NULL )
2021-05-18 15:18:14 +10:00
impl - > auto_profile = spa_atob ( s ) ;
2021-01-27 13:43:12 +01:00
if ( ( s = acp_dict_lookup ( props , " api.acp.auto-port " ) ) ! = NULL )
2021-05-18 15:18:14 +10:00
impl - > auto_port = spa_atob ( s ) ;
2022-12-16 12:57:17 +01:00
if ( ( s = acp_dict_lookup ( props , " api.acp.probe-rate " ) ) ! = NULL )
impl - > rate = atoi ( s ) ;
2023-01-31 15:59:39 +01:00
if ( ( s = acp_dict_lookup ( props , " api.acp.pro-channels " ) ) ! = NULL )
impl - > pro_channels = atoi ( s ) ;
2024-12-07 13:27:09 +02:00
if ( ( s = acp_dict_lookup ( props , " api.alsa.split-enable " ) ) ! = NULL )
impl - > ucm . split_enable = spa_atob ( s ) ;
2025-07-02 08:09:54 -04:00
if ( ( s = acp_dict_lookup ( props , " api.acp.disable-pro-audio " ) ) ! = NULL )
impl - > disable_pro_audio = spa_atob ( s ) ;
2025-10-09 20:24:14 -07:00
if ( ( s = acp_dict_lookup ( props , " api.acp.use-eld-channels " ) ) ! = NULL )
impl - > use_eld_channels = spa_atob ( s ) ;
2020-05-15 19:42:15 +02:00
}
2024-12-07 13:27:09 +02:00
# if SND_LIB_VERSION < 0x10207
if ( impl - > ucm . split_enable )
pa_log_info ( " alsa-lib too old for PipeWire-side UCM SplitPCM " ) ;
impl - > ucm . split_enable = false ; /* API addition in 1.2.7 */
# endif
2020-05-15 19:42:15 +02:00
impl - > ucm . default_sample_spec . format = PA_SAMPLE_S16NE ;
2022-12-16 12:57:17 +01:00
impl - > ucm . default_sample_spec . rate = impl - > rate ;
2020-05-15 19:42:15 +02:00
impl - > ucm . default_sample_spec . channels = 2 ;
pa_channel_map_init_extend ( & impl - > ucm . default_channel_map ,
impl - > ucm . default_sample_spec . channels , PA_CHANNEL_MAP_ALSA ) ;
impl - > ucm . default_n_fragments = 4 ;
impl - > ucm . default_fragment_size_msec = 25 ;
impl - > ucm . mixers = pa_hashmap_new_full ( pa_idxset_string_hash_func ,
pa_idxset_string_compare_func ,
pa_xfree , ( pa_free_cb_t ) pa_alsa_mixer_free ) ;
impl - > profiles = pa_hashmap_new_full ( pa_idxset_string_hash_func ,
pa_idxset_string_compare_func , NULL ,
( pa_free_cb_t ) profile_free ) ;
impl - > ports = pa_hashmap_new_full ( pa_idxset_string_hash_func ,
pa_idxset_string_compare_func , NULL ,
( pa_free_cb_t ) port_free ) ;
snd_config_update_free_global ( ) ;
2020-09-30 12:56:05 +02:00
res = impl - > use_ucm ? pa_alsa_ucm_query_profiles ( & impl - > ucm , card - > index ) : - 1 ;
if ( res = = - PA_ALSA_ERR_UCM_LINKED ) {
2023-03-23 13:24:22 +01:00
res = - EEXIST ;
2020-09-30 12:56:05 +02:00
goto error ;
}
if ( res = = 0 ) {
2020-05-15 19:42:15 +02:00
pa_log_info ( " Found UCM profiles " ) ;
impl - > profile_set = pa_alsa_ucm_add_profile_set ( & impl - > ucm , & impl - > ucm . default_channel_map ) ;
} else {
impl - > use_ucm = false ;
impl - > profile_set = pa_alsa_profile_set_new ( profile_set , & impl - > ucm . default_channel_map ) ;
}
2020-09-30 12:56:05 +02:00
if ( impl - > profile_set = = NULL ) {
res = - ENOTSUP ;
goto error ;
}
2020-05-15 19:42:15 +02:00
2022-12-06 13:39:45 +01:00
impl - > profile_set - > ignore_dB = impl - > ignore_dB ;
2020-05-15 19:42:15 +02:00
pa_alsa_profile_set_probe ( impl - > profile_set , impl - > ucm . mixers ,
device_id ,
& impl - > ucm . default_sample_spec ,
impl - > ucm . default_n_fragments ,
impl - > ucm . default_fragment_size_msec ) ;
pa_alsa_init_proplist_card ( NULL , impl - > proplist , impl - > card . index ) ;
pa_proplist_sets ( impl - > proplist , PA_PROP_DEVICE_STRING , device_id ) ;
pa_alsa_init_description ( impl - > proplist , NULL ) ;
add_profiles ( impl ) ;
2020-12-17 11:03:31 +01:00
prune_singleton_availability_groups ( impl - > ports ) ;
2020-05-15 19:42:15 +02:00
card - > n_profiles = pa_dynarray_size ( & impl - > out . profiles ) ;
card - > profiles = impl - > out . profiles . array . data ;
card - > n_ports = pa_dynarray_size ( & impl - > out . ports ) ;
card - > ports = impl - > out . ports . array . data ;
card - > n_devices = pa_dynarray_size ( & impl - > out . devices ) ;
card - > devices = impl - > out . devices . array . data ;
pa_proplist_as_dict ( impl - > proplist , & card - > props ) ;
init_jacks ( impl ) ;
2021-01-27 13:43:12 +01:00
if ( ! impl - > auto_profile & & profile = = NULL )
profile = " off " ;
spa: alsa: autodetect supported iec958 codecs via ELD info
The alsa/acp code already supports getting a user-friendly monitor name
using the EDID-Like Data (ELD) information available from cards that follow
the Intel HDA specification.
This patch adds support for also parsing the SAD fields of the ELD, and
exposing the results as a "iec958.codecs.detected" property on the
corresponding node, which should make it possible to provide more
user-friendly configuration UIs and defaults.
The default value will take effect if the session manager does not set a
different value.
Brief example:
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
"iec958.codecs.detected": "[\"PCM\",\"AC3\",\"EAC3\",\"TrueHD\"]",
<after powering on my receiver>
test@test:~/checkouts/pipewire$ pw-dump | grep -E "(iec958.codecs.detected|iec958.codecs)\":"
"iec958.codecs": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
"iec958.codecs.detected": "[\"PCM\",\"DTS\",\"AC3\",\"EAC3\",\"TrueHD\",\"DTS-HD\"]",
Big thanks to Pauli Virtanen <pav@iki.fi>, who also wrote large paths of the
code for this patch.
2024-11-25 22:35:28 +01:00
init_eld_ctls ( impl ) ;
2020-09-08 16:39:47 +02:00
profile_index = acp_card_find_best_profile_index ( & impl - > card , profile ) ;
2021-02-22 16:42:29 +01:00
acp_card_set_profile ( & impl - > card , profile_index , 0 ) ;
2020-05-15 19:42:15 +02:00
return & impl - > card ;
2020-09-30 12:56:05 +02:00
error :
pa_alsa_refcnt_dec ( ) ;
free ( impl ) ;
errno = - res ;
return NULL ;
2020-05-15 19:42:15 +02:00
}
void acp_card_add_listener ( struct acp_card * card ,
const struct acp_card_events * events , void * user_data )
{
pa_card * impl = ( pa_card * ) card ;
impl - > events = events ;
impl - > user_data = user_data ;
}
void acp_card_destroy ( struct acp_card * card )
{
2020-09-30 12:56:05 +02:00
pa_card * impl = ( pa_card * ) card ;
2020-11-09 15:01:07 +01:00
if ( impl - > profiles )
pa_hashmap_free ( impl - > profiles ) ;
if ( impl - > ports )
pa_hashmap_free ( impl - > ports ) ;
pa_dynarray_clear ( & impl - > out . devices ) ;
pa_dynarray_clear ( & impl - > out . profiles ) ;
pa_dynarray_clear ( & impl - > out . ports ) ;
if ( impl - > ucm . mixers )
pa_hashmap_free ( impl - > ucm . mixers ) ;
if ( impl - > jacks )
pa_hashmap_free ( impl - > jacks ) ;
if ( impl - > profile_set )
pa_alsa_profile_set_free ( impl - > profile_set ) ;
pa_alsa_ucm_free ( & impl - > ucm ) ;
pa_proplist_free ( impl - > proplist ) ;
2020-05-15 19:42:15 +02:00
pa_alsa_refcnt_dec ( ) ;
2020-09-30 12:56:05 +02:00
free ( impl ) ;
2020-05-15 19:42:15 +02:00
}
int acp_card_poll_descriptors_count ( struct acp_card * card )
{
pa_card * impl = ( pa_card * ) card ;
void * state ;
pa_alsa_mixer * pm ;
int n , count = 0 ;
PA_HASHMAP_FOREACH ( pm , impl - > ucm . mixers , state ) {
if ( ! pm - > used_for_poll )
continue ;
n = snd_mixer_poll_descriptors_count ( pm - > mixer_handle ) ;
if ( n < 0 )
return n ;
count + = n ;
}
return count ;
}
int acp_card_poll_descriptors ( struct acp_card * card , struct pollfd * pfds , unsigned int space )
{
pa_card * impl = ( pa_card * ) card ;
void * state ;
pa_alsa_mixer * pm ;
int n , count = 0 ;
PA_HASHMAP_FOREACH ( pm , impl - > ucm . mixers , state ) {
if ( ! pm - > used_for_poll )
continue ;
n = snd_mixer_poll_descriptors ( pm - > mixer_handle , pfds , space ) ;
if ( n < 0 )
return n ;
if ( space > = ( unsigned int ) n ) {
count + = n ;
space - = n ;
pfds + = n ;
} else
space = 0 ;
}
return count ;
}
int acp_card_poll_descriptors_revents ( struct acp_card * card , struct pollfd * pfds ,
unsigned int nfds , unsigned short * revents )
{
unsigned int idx ;
unsigned short res ;
if ( nfds = = 0 )
return - EINVAL ;
res = 0 ;
for ( idx = 0 ; idx < nfds ; idx + + , pfds + + )
res | = pfds - > revents & ( POLLIN | POLLERR | POLLNVAL ) ;
* revents = res ;
return 0 ;
}
int acp_card_handle_events ( struct acp_card * card )
{
pa_card * impl = ( pa_card * ) card ;
void * state ;
pa_alsa_mixer * pm ;
int n , count = 0 ;
PA_HASHMAP_FOREACH ( pm , impl - > ucm . mixers , state ) {
if ( ! pm - > used_for_poll )
continue ;
n = snd_mixer_handle_events ( pm - > mixer_handle ) ;
if ( n < 0 )
return n ;
count + = n ;
}
return count ;
}
static void sync_mixer ( pa_alsa_device * d , pa_device_port * port )
{
pa_alsa_setting * setting = NULL ;
2024-09-23 15:34:08 +02:00
pa_card * impl = d - > card ;
2020-05-15 19:42:15 +02:00
if ( ! d - > mixer_path )
return ;
/* port may be NULL, because if we use a synthesized mixer path, then the
* sink has no ports . */
if ( port & & ! d - > ucm_context ) {
pa_alsa_port_data * data ;
data = PA_DEVICE_PORT_DATA ( port ) ;
setting = data - > setting ;
}
2024-09-24 13:14:17 +02:00
if ( d - > mixer_handle & & ! impl - > disable_mixer_path )
2022-08-03 12:34:13 +02:00
pa_alsa_path_select ( d - > mixer_path , setting , d - > mixer_handle , d - > muted ) ;
2020-05-15 19:42:15 +02:00
if ( d - > set_mute )
d - > set_mute ( d , d - > muted ) ;
if ( d - > set_volume )
d - > set_volume ( d , & d - > real_volume ) ;
}
2020-09-08 16:39:47 +02:00
uint32_t acp_device_find_best_port_index ( struct acp_device * dev , const char * name )
{
uint32_t i ;
2020-09-09 14:08:15 +02:00
uint32_t best , best2 , best3 ;
2020-09-08 16:39:47 +02:00
struct acp_port * * ports = dev - > ports ;
2020-09-09 14:08:15 +02:00
best = best2 = best3 = ACP_INVALID_INDEX ;
2020-09-08 16:39:47 +02:00
for ( i = 0 ; i < dev - > n_ports ; i + + ) {
struct acp_port * p = ports [ i ] ;
2023-11-30 17:40:48 +01:00
if ( SPA_FLAG_IS_SET ( p - > flags , ACP_PORT_HIDDEN ) )
continue ;
2020-09-08 16:39:47 +02:00
if ( name ) {
2021-05-18 11:36:13 +10:00
if ( spa_streq ( name , p - > name ) )
2020-09-08 16:39:47 +02:00
best = i ;
2021-01-27 13:04:24 +01:00
} else if ( p - > available = = ACP_AVAILABLE_YES ) {
2020-09-08 16:39:47 +02:00
if ( best = = ACP_INVALID_INDEX | | p - > priority > ports [ best ] - > priority )
best = i ;
2020-09-09 14:08:15 +02:00
} else if ( p - > available ! = ACP_AVAILABLE_NO ) {
if ( best2 = = ACP_INVALID_INDEX | | p - > priority > ports [ best2 ] - > priority )
best2 = i ;
2020-09-08 16:39:47 +02:00
} else {
2020-09-09 14:08:15 +02:00
if ( best3 = = ACP_INVALID_INDEX | | p - > priority > ports [ best3 ] - > priority )
best3 = i ;
2020-09-08 16:39:47 +02:00
}
}
if ( best = = ACP_INVALID_INDEX )
2020-09-09 14:08:15 +02:00
best = best2 ;
if ( best = = ACP_INVALID_INDEX )
best = best3 ;
2020-09-08 16:39:47 +02:00
if ( best = = ACP_INVALID_INDEX )
best = 0 ;
2020-10-08 18:04:34 +02:00
if ( best < dev - > n_ports )
return ports [ best ] - > index ;
else
return ACP_INVALID_INDEX ;
2020-09-08 16:39:47 +02:00
}
2020-05-15 19:42:15 +02:00
2021-02-22 16:42:29 +01:00
int acp_device_set_port ( struct acp_device * dev , uint32_t port_index , uint32_t flags )
2020-05-15 19:42:15 +02:00
{
pa_alsa_device * d = ( pa_alsa_device * ) dev ;
pa_card * impl = d - > card ;
2020-07-08 16:16:43 +02:00
pa_device_port * p , * old = d - > active_port ;
int res ;
2020-05-15 19:42:15 +02:00
if ( port_index > = impl - > card . n_ports )
return - EINVAL ;
p = ( pa_device_port * ) impl - > card . ports [ port_index ] ;
2020-08-18 12:31:03 +02:00
if ( ! pa_hashmap_get ( d - > ports , p - > name ) )
2020-05-15 19:42:15 +02:00
return - EINVAL ;
2023-11-30 17:40:48 +01:00
if ( SPA_FLAG_IS_SET ( p - > port . flags , ACP_PORT_HIDDEN ) )
return - EINVAL ;
2020-05-15 19:42:15 +02:00
2021-03-30 16:06:30 +02:00
p - > port . flags = ACP_PORT_ACTIVE | flags ;
if ( p = = old )
return 0 ;
2020-07-08 16:16:43 +02:00
if ( old )
2021-02-22 16:42:29 +01:00
old - > port . flags & = ~ ( ACP_PORT_ACTIVE | ACP_PORT_SAVE ) ;
2020-05-15 19:42:15 +02:00
d - > active_port = p ;
if ( impl - > use_ucm ) {
pa_alsa_ucm_port_data * data ;
data = PA_DEVICE_PORT_DATA ( p ) ;
d - > mixer_path = data - > path ;
2020-12-01 10:43:39 +01:00
mixer_volume_init ( impl , d ) ;
2020-05-15 19:42:15 +02:00
2023-11-27 23:49:27 +03:00
res = pa_alsa_ucm_set_port ( d - > ucm_context , p ) ;
2024-09-25 10:02:46 +02:00
sync_mixer ( d , p ) ;
2020-05-15 19:42:15 +02:00
} else {
pa_alsa_port_data * data ;
data = PA_DEVICE_PORT_DATA ( p ) ;
d - > mixer_path = data - > path ;
2020-12-01 10:43:39 +01:00
mixer_volume_init ( impl , d ) ;
2020-05-15 19:42:15 +02:00
sync_mixer ( d , p ) ;
2020-07-08 16:16:43 +02:00
res = 0 ;
2020-05-15 19:42:15 +02:00
#if 0
if ( data - > suspend_when_unavailable & & p - > available = = PA_AVAILABLE_NO )
pa_sink_suspend ( s , true , PA_SUSPEND_UNAVAILABLE ) ;
else
pa_sink_suspend ( s , false , PA_SUSPEND_UNAVAILABLE ) ;
# endif
}
2020-07-08 16:16:43 +02:00
if ( impl - > events & & impl - > events - > port_changed )
impl - > events - > port_changed ( impl - > user_data ,
old ? old - > port . index : 0 , p - > port . index ) ;
return res ;
2020-05-15 19:42:15 +02:00
}
int acp_device_set_volume ( struct acp_device * dev , const float * volume , uint32_t n_volume )
{
pa_alsa_device * d = ( pa_alsa_device * ) dev ;
2020-07-06 17:22:42 +02:00
pa_card * impl = d - > card ;
2020-05-15 19:42:15 +02:00
uint32_t i ;
2020-07-08 17:24:23 +02:00
pa_cvolume v , old_volume ;
2020-05-15 19:42:15 +02:00
if ( n_volume = = 0 )
return - EINVAL ;
2020-07-06 17:22:42 +02:00
2020-07-08 17:24:23 +02:00
old_volume = d - > real_volume ;
2020-07-06 17:22:42 +02:00
v . channels = d - > mapping - > channel_map . channels ;
2020-05-15 19:42:15 +02:00
for ( i = 0 ; i < v . channels ; i + + )
2021-05-20 11:20:19 +10:00
v . values [ i ] = pa_sw_volume_from_linear ( volume [ i % n_volume ] ) ;
2020-07-06 17:22:42 +02:00
2021-05-10 10:50:42 +02:00
pa_log_info ( " Set %s volume: min:%d max:%d " ,
d - > set_volume ? " hardware " : " software " ,
pa_cvolume_min ( & v ) , pa_cvolume_max ( & v ) ) ;
2020-07-08 16:16:43 +02:00
for ( i = 0 ; i < v . channels ; i + + )
pa_log_debug ( " %d: %d " , i , v . values [ i ] ) ;
2020-07-06 17:22:42 +02:00
if ( d - > set_volume ) {
2020-05-15 19:42:15 +02:00
d - > set_volume ( d , & v ) ;
2020-07-06 17:22:42 +02:00
} else {
d - > real_volume = v ;
2020-09-28 12:40:16 +02:00
d - > soft_volume = v ;
2020-07-06 17:22:42 +02:00
}
2020-07-08 17:24:23 +02:00
if ( ! pa_cvolume_equal ( & d - > real_volume , & old_volume ) )
if ( impl - > events & & impl - > events - > volume_changed )
impl - > events - > volume_changed ( impl - > user_data , dev ) ;
2020-05-15 19:42:15 +02:00
return 0 ;
}
2021-03-21 16:19:57 +01:00
static int get_volume ( pa_cvolume * v , float * volume , uint32_t n_volume )
2020-05-15 19:42:15 +02:00
{
uint32_t i ;
2021-03-21 16:19:57 +01:00
if ( v - > channels = = 0 )
2020-05-15 19:42:15 +02:00
return - EIO ;
for ( i = 0 ; i < n_volume ; i + + )
2024-06-18 12:17:56 +02:00
volume [ i ] = ( float ) pa_sw_volume_to_linear ( v - > values [ i % v - > channels ] ) ;
2020-05-15 19:42:15 +02:00
return 0 ;
}
2021-03-21 16:19:57 +01:00
int acp_device_get_soft_volume ( struct acp_device * dev , float * volume , uint32_t n_volume )
{
pa_alsa_device * d = ( pa_alsa_device * ) dev ;
return get_volume ( & d - > soft_volume , volume , n_volume ) ;
}
int acp_device_get_volume ( struct acp_device * dev , float * volume , uint32_t n_volume )
{
pa_alsa_device * d = ( pa_alsa_device * ) dev ;
return get_volume ( & d - > real_volume , volume , n_volume ) ;
}
2020-05-15 19:42:15 +02:00
int acp_device_set_mute ( struct acp_device * dev , bool mute )
{
pa_alsa_device * d = ( pa_alsa_device * ) dev ;
2020-07-06 17:22:42 +02:00
pa_card * impl = d - > card ;
2020-07-08 17:24:23 +02:00
bool old_muted = d - > muted ;
if ( old_muted = = mute )
2020-07-06 17:22:42 +02:00
return 0 ;
2020-07-08 16:16:43 +02:00
pa_log_info ( " Set %s mute: %d " , d - > set_mute ? " hardware " : " software " , mute ) ;
2020-07-06 17:22:42 +02:00
if ( d - > set_mute ) {
2020-05-15 19:42:15 +02:00
d - > set_mute ( d , mute ) ;
2020-07-06 17:22:42 +02:00
} else {
d - > muted = mute ;
}
2020-07-08 17:24:23 +02:00
if ( old_muted ! = mute )
if ( impl - > events & & impl - > events - > mute_changed )
impl - > events - > mute_changed ( impl - > user_data , dev ) ;
2020-05-15 19:42:15 +02:00
return 0 ;
}
int acp_device_get_mute ( struct acp_device * dev , bool * mute )
{
pa_alsa_device * d = ( pa_alsa_device * ) dev ;
* mute = d - > muted ;
return 0 ;
}
2020-07-06 17:22:42 +02:00
2020-05-15 19:42:15 +02:00
void acp_set_log_func ( acp_log_func func , void * data )
{
_acp_log_func = func ;
_acp_log_data = data ;
}
void acp_set_log_level ( int level )
{
_acp_log_level = level ;
}