diff options
| -rw-r--r-- | src/modules/alsa-util.c | 132 | ||||
| -rw-r--r-- | src/modules/alsa-util.h | 2 | ||||
| -rw-r--r-- | src/modules/module-alsa-sink.c | 52 | ||||
| -rw-r--r-- | src/modules/module-alsa-source.c | 54 | 
4 files changed, 192 insertions, 48 deletions
diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c index 5b74c98c..40170e9c 100644 --- a/src/modules/alsa-util.c +++ b/src/modules/alsa-util.c @@ -310,8 +310,10 @@ int pa_alsa_set_hw_params(      buffer_size = *periods * *period_size; -    if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 || -        (ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) +    if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) +        goto finish; + +    if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0)          goto finish;      if (_use_mmap) { @@ -627,7 +629,7 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) {      pa_assert(dev);      if ((err = snd_mixer_attach(mixer, dev)) < 0) { -        pa_log_warn("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); +        pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err));          return -1;      } @@ -641,6 +643,8 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) {          return -1;      } +    pa_log_info("Successfully attached to mixer '%s'", dev); +      return 0;  } @@ -656,7 +660,7 @@ snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const      snd_mixer_selem_id_set_name(sid, name);      if (!(elem = snd_mixer_find_selem(mixer, sid))) { -        pa_log_warn("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); +        pa_log_info("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));          if (fallback) {              snd_mixer_selem_id_set_name(sid, fallback); @@ -671,3 +675,123 @@ snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const      return elem;  } + +static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { +    [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ + +    [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, +    [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, +    [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + +    [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, +    [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, +    [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + +    [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, + +    [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, +    [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + +    [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX9] =  SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN +}; + + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { +    unsigned i; +    pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; +    pa_bool_t mono_used = FALSE; + +    pa_assert(elem); +    pa_assert(channel_map); +    pa_assert(mixer_map); + +    memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); + +    if (channel_map->channels > 1 && +        ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || +         (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { +        pa_log_info("ALSA device lacks independant volume controls for each channel, falling back to software volume control."); +        return -1; +    } + +    for (i = 0; i < channel_map->channels; i++) { +        snd_mixer_selem_channel_id_t id; +        pa_bool_t is_mono; + +        is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; +        id = alsa_channel_ids[channel_map->map[i]]; + +        if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { +            pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer. Falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); +            return -1; +        } + +        if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { +            pa_log_info("Channel map has duplicate channel '%s', failling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); +            return -1; +        } + +        if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || +            (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { + +            pa_log_info("ALSA device lacks separate volumes control for channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); +            return -1; +        } + +        if (is_mono) { +            mixer_map[i] = SND_MIXER_SCHN_MONO; +            mono_used = TRUE; +        } else { +            mixer_map[i] = id; +            alsa_channel_used[id] = TRUE; +        } +    } + +    pa_log_info("All %u channels can be mapped to mixer channels. Using hardware volume control.", channel_map->channels); + +    return 0; +} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h index 36720b03..53d9a2fb 100644 --- a/src/modules/alsa-util.h +++ b/src/modules/alsa-util.h @@ -71,4 +71,6 @@ snd_pcm_t *pa_alsa_open_by_device_string(          snd_pcm_uframes_t *period_size,          pa_bool_t *use_mmap); +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); +  #endif diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index e62b8a06..14aef7c9 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -95,6 +95,8 @@ struct userdata {      pa_bool_t first;      pa_rtpoll_item *alsa_rtpoll_item; + +    snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];  };  static const char* const valid_modargs[] = { @@ -505,9 +507,9 @@ static int sink_get_volume_cb(pa_sink *s) {      for (i = 0; i < s->sample_spec.channels; i++) {          long set_vol, vol; -        pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); +        pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); -        if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, i, &vol)) < 0) +        if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0)              goto fail;          set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; @@ -539,7 +541,7 @@ static int sink_set_volume_cb(pa_sink *s) {          long alsa_vol;          pa_volume_t vol; -        pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); +        pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i]));          vol = s->volume.values[i]; @@ -548,7 +550,7 @@ static int sink_set_volume_cb(pa_sink *s) {          alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; -        if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, i, alsa_vol)) < 0) +        if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)              goto fail;      } @@ -826,10 +828,25 @@ int pa__init(pa_module*m) {      if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0)          pa_log_warn("Error opening mixer: %s", snd_strerror(err));      else { +        pa_bool_t found = FALSE; + +        if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) +            found = TRUE; +        else { +            char *md = pa_sprintf_malloc("hw:%s", dev_id); + +            if (strcmp(u->device_name, md)) +                if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) +                    found = TRUE; -        if ((pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) < 0) || -            !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) { +            pa_xfree(md); +        } + +        if (found) +            if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) +                found = FALSE; +        if (!found) {              snd_mixer_close(u->mixer_handle);              u->mixer_handle = NULL;          } @@ -863,7 +880,7 @@ int pa__init(pa_module*m) {                                      use_mmap ? " via DMA" : ""));      pa_xfree(t); -    u->sink->flags = PA_SINK_HARDWARE|PA_SINK_HW_VOLUME_CTRL|PA_SINK_LATENCY; +    u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY;      u->frame_size = frame_size;      u->fragment_size = frag_size = period_size * frame_size; @@ -875,35 +892,26 @@ int pa__init(pa_module*m) {      pa_memchunk_reset(&u->memchunk);      if (u->mixer_handle) { -        /* Initialize mixer code */ -          pa_assert(u->mixer_elem); -        if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { -            int i; - -            for (i = 0; i < ss.channels; i++) -                if (!snd_mixer_selem_has_playback_channel(u->mixer_elem, i)) -                    break; - -            if (i == ss.channels) { -                pa_log_debug("ALSA device has separate volumes controls for all %u channels.", ss.channels); +        if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) +            if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0) {                  u->sink->get_volume = sink_get_volume_cb;                  u->sink->set_volume = sink_set_volume_cb;                  snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); -            } else -                pa_log_info("ALSA device lacks separate volumes controls for all %u channels (%u available), falling back to software volume control.", ss.channels, i+1); -        } +                u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; +            }          if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) {              u->sink->get_mute = sink_get_mute_cb;              u->sink->set_mute = sink_set_mute_cb; +            u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;          }          u->mixer_fdl = pa_alsa_fdlist_new();          if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { -            pa_log("failed to initialise file descriptor monitoring"); +            pa_log("Failed to initialize file descriptor monitoring");              goto fail;          } diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c index 2d2bfa07..23a2f921 100644 --- a/src/modules/module-alsa-source.c +++ b/src/modules/module-alsa-source.c @@ -93,6 +93,8 @@ struct userdata {      pa_bool_t use_mmap;      pa_rtpoll_item *alsa_rtpoll_item; + +    snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];  };  static const char* const valid_modargs[] = { @@ -494,9 +496,9 @@ static int source_get_volume_cb(pa_source *s) {      for (i = 0; i < s->sample_spec.channels; i++) {          long set_vol, vol; -        pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); +        pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); -        if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, i, &vol)) < 0) +        if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0)              goto fail;          set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; @@ -528,7 +530,7 @@ static int source_set_volume_cb(pa_source *s) {          long alsa_vol;          pa_volume_t vol; -        pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); +        pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i]));          vol = s->volume.values[i]; @@ -537,7 +539,7 @@ static int source_set_volume_cb(pa_source *s) {          alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; -        if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, i, alsa_vol)) < 0) +        if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)              goto fail;      } @@ -753,6 +755,8 @@ int pa__init(pa_module*m) {      snd_config_update_free_global(); +    b = use_mmap; +      if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {          if (!(u->pcm_handle = pa_alsa_open_by_device_id( @@ -800,16 +804,28 @@ int pa__init(pa_module*m) {      /* ALSA might tweak the sample spec, so recalculate the frame size */      frame_size = pa_frame_size(&ss); -    if (ss.channels != map.channels) -        /* Seems ALSA didn't like the channel number, so let's fix the channel map */ -        pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); -      if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0)          pa_log("Error opening mixer: %s", snd_strerror(err));      else { +        pa_bool_t found = FALSE; + +        if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) +            found = TRUE; +        else { +            char *md = pa_sprintf_malloc("hw:%s", dev_id); + +            if (strcmp(u->device_name, md)) +                if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) +                    found = TRUE; + +            pa_xfree(md); +        } -        if ((pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) < 0) || -            !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", NULL))) { +        if (found) +            if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic"))) +                found = FALSE; + +        if (!found) {              snd_mixer_close(u->mixer_handle);              u->mixer_handle = NULL;          } @@ -843,7 +859,7 @@ int pa__init(pa_module*m) {                                        use_mmap ? " via DMA" : ""));      pa_xfree(t); -    u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL; +    u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY;      u->frame_size = frame_size;      u->fragment_size = frag_size = period_size * frame_size; @@ -855,30 +871,24 @@ int pa__init(pa_module*m) {      if (u->mixer_handle) {          pa_assert(u->mixer_elem); -        if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { -            int i; - -            for (i = 0;i < ss.channels;i++) { -                if (!snd_mixer_selem_has_capture_channel(u->mixer_elem, i)) -                    break; -            } - -            if (i == ss.channels) { +        if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) +            if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0) {                  u->source->get_volume = source_get_volume_cb;                  u->source->set_volume = source_set_volume_cb;                  snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); +                u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;              } -        }          if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) {              u->source->get_mute = source_get_mute_cb;              u->source->set_mute = source_set_mute_cb; +            u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;          }          u->mixer_fdl = pa_alsa_fdlist_new();          if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { -            pa_log("failed to initialise file descriptor monitoring"); +            pa_log("Failed to initialize file descriptor monitoring");              goto fail;          }  | 
