Hi there, when using the newly added feature for adjusting the output volume of ALSA devices (see https://trac.pjsip.org/repos/ticket/1991), I noticed a couple of problems: a) The very first mixer is used always. Even if it is the wrong card! The master mixer must be managed in a card specific manner. b) The output_vol parameter is not used when calling open_playback() and the capability is set. c) The output volume capability is not reported to via the alsa_factory_get_dev_info() function. The attached patch fixes all of the mentioned problems and includes some small other improvements (e.g. parameter checks, removal of some unused macros). Please integrate. Thank a lot in advance. Regrads, Michael
Index: pjmedia/src/pjmedia-audiodev/alsa_dev.c =================================================================== --- pjmedia/src/pjmedia-audiodev/alsa_dev.c (revision 5883) +++ pjmedia/src/pjmedia-audiodev/alsa_dev.c (working copy) @@ -39,12 +39,10 @@ #define THIS_FILE "alsa_dev.c" #define ALSA_DEVICE_NAME "plughw:%d,%d" -#define ALSASOUND_PLAYBACK 1 -#define ALSASOUND_CAPTURE 2 #define MAX_SOUND_CARDS 5 -#define MAX_SOUND_DEVICES_PER_CARD 5 #define MAX_DEVICES 32 -#define MAX_MIX_NAME_LEN 64 +#define MAX_MIX_NAME_LEN 64 +#define MAX_CARD_HCTL_LEN 8 /* Set to 1 to enable tracing */ #if 0 @@ -89,6 +87,15 @@ static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); +typedef struct alsa_card_info +{ + unsigned id; + char name[PJMEDIA_AUD_DEV_INFO_NAME_LEN]; + char hctl[MAX_CARD_HCTL_LEN]; + char pb_mixer_name[MAX_MIX_NAME_LEN]; +} alsa_card_info; + + struct alsa_factory { pjmedia_aud_dev_factory base; @@ -96,9 +103,11 @@ pj_pool_t *pool; pj_pool_t *base_pool; + unsigned card_cnt; + alsa_card_info cards[MAX_SOUND_CARDS]; + unsigned dev_cnt; pjmedia_aud_dev_info devs[MAX_DEVICES]; - char pb_mixer_name[MAX_MIX_NAME_LEN]; }; struct alsa_stream @@ -110,7 +119,6 @@ struct alsa_factory *af; void *user_data; pjmedia_aud_param param; /* Running parameter */ - int rec_id; /* Capture device id */ int quit; /* Playback */ @@ -151,6 +159,8 @@ &alsa_stream_destroy }; +static void get_mixer_name(alsa_card_info *card); + static void null_alsa_error_handler (const char *file, int line, const char *function, @@ -210,6 +220,38 @@ } +static pj_status_t add_card (struct alsa_factory *af, unsigned id) +{ + alsa_card_info *card; + char *name; + + if (af->card_cnt >= PJ_ARRAY_SIZE(af->cards)) + return PJ_ETOOMANY; + + card = &af->cards[af->card_cnt]; + + if (snd_card_get_name(id, &name) == 0) { + card->id = id; + + strncpy(card->name, name, sizeof(card->name) - 1); + free(name); + + snprintf(af->cards[af->card_cnt].hctl, + sizeof(af->cards[af->card_cnt].hctl), "hw:%d", id); + + get_mixer_name(card); + + ++af->card_cnt; + + PJ_LOG (5,(THIS_FILE, "Added sound card %s", card->name)); + + return PJ_SUCCESS; + } + + return PJMEDIA_EAUD_INVDEV; +} + + static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) { pjmedia_aud_dev_info *adi; @@ -275,7 +317,8 @@ return PJ_SUCCESS; } -static void get_mixer_name(struct alsa_factory *af) + +static void get_mixer_name(alsa_card_info *card) { snd_mixer_t *handle; snd_mixer_elem_t *elem; @@ -283,7 +326,7 @@ if (snd_mixer_open(&handle, 0) < 0) return; - if (snd_mixer_attach(handle, "default") < 0) { + if (snd_mixer_attach(handle, card->hctl) < 0) { snd_mixer_close(handle); return; } @@ -304,9 +347,10 @@ if (snd_mixer_selem_is_active(elem) && snd_mixer_selem_has_playback_volume(elem)) { - pj_ansi_strncpy(af->pb_mixer_name, snd_mixer_selem_get_name(elem), - sizeof(af->pb_mixer_name)); - TRACE_((THIS_FILE, "Playback mixer name: %s", af->pb_mixer_name)); + pj_ansi_strncpy(card->pb_mixer_name, snd_mixer_selem_get_name(elem), + sizeof(card->pb_mixer_name)); + TRACE_((THIS_FILE, "Playback mixer name for card %d: %s", card->id, + card->mixer_name)); break; } } @@ -367,6 +411,7 @@ static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f) { struct alsa_factory *af = (struct alsa_factory*)f; + int card = -1; char **hints, **n; int err; @@ -378,8 +423,22 @@ } af->pool = pj_pool_create(af->pf, "alsa_aud", 256, 256, NULL); + af->card_cnt = 0; af->dev_cnt = 0; + /* Enumerate sound cards */ + err = snd_card_next(&card); + + while ((err == 0) && (card != -1) ) { + if (add_card(af, card) == PJ_SUCCESS) + err = snd_card_next(&card); + else + err = -1; + } + + if (err != 0) + return PJMEDIA_EAUD_SYSERR; + /* Enumerate sound devices */ err = snd_device_name_hint(-1, "pcm", (void***)&hints); if (err != 0) @@ -399,9 +458,6 @@ n++; } - /* Get the mixer name */ - get_mixer_name(af); - /* Install error handler after enumeration, otherwise we'll get many * error messages about invalid card/device ID. */ @@ -434,10 +490,13 @@ pj_memcpy(info, &af->devs[index], sizeof(*info)); info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | - PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + return PJ_SUCCESS; } + /* API: create default parameter */ static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f, unsigned index, @@ -531,7 +590,6 @@ } - static int ca_thread_func (void *arg) { struct alsa_stream* stream = (struct alsa_stream*) arg; @@ -664,8 +722,15 @@ /* Set number of channels */ TRACE_((THIS_FILE, "open_playback: set channels: %d", param->channel_count)); - snd_pcm_hw_params_set_channels (stream->pb_pcm, params, - param->channel_count); + result = snd_pcm_hw_params_set_channels (stream->pb_pcm, params, + param->channel_count); + if (result < 0) { + PJ_LOG (3,(THIS_FILE, "Unable to set a channel count of %d for " + "playback device '%s'", param->channel_count, + stream->af->devs[param->play_id].name)); + snd_pcm_close (stream->pb_pcm); + return PJMEDIA_EAUD_SYSERR; + } /* Set clock rate */ rate = param->clock_rate; @@ -710,6 +775,18 @@ return PJMEDIA_EAUD_SYSERR; } + /* Apply output volume adjustment if provided */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + pj_status_t status = alsa_stream_set_cap(stream, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + ¶m->output_vol); + + if (status != PJ_SUCCESS) + PJ_LOG (4,(THIS_FILE, "Unable to set the output volume %d for " + "playback device '%s'", param->output_vol, + stream->af->devs[param->play_id].name)); + } + PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", stream->af->devs[param->play_id].name, @@ -782,8 +859,15 @@ /* Set number of channels */ TRACE_((THIS_FILE, "open_capture: set channels: %d", param->channel_count)); - snd_pcm_hw_params_set_channels (stream->ca_pcm, params, - param->channel_count); + result = snd_pcm_hw_params_set_channels (stream->ca_pcm, params, + param->channel_count); + if (result < 0) { + PJ_LOG (3,(THIS_FILE, "Unable to set a channel count of %d for " + "capture device '%s'", param->channel_count, + stream->af->devs[param->rec_id].name)); + snd_pcm_close (stream->ca_pcm); + return PJMEDIA_EAUD_SYSERR; + } /* Set clock rate */ rate = param->clock_rate; @@ -915,18 +999,21 @@ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); - if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && - (stream->param.dir & PJMEDIA_DIR_CAPTURE)) - { + if ((cap == PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) && + (stream->param.dir & PJMEDIA_DIR_CAPTURE)) { /* Recording latency */ *(unsigned*)pval = stream->param.input_latency_ms; return PJ_SUCCESS; - } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && - (stream->param.dir & PJMEDIA_DIR_PLAYBACK)) - { + } else if ((cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) && + (stream->param.dir & PJMEDIA_DIR_PLAYBACK)) { /* Playback latency */ *(unsigned*)pval = stream->param.output_latency_ms; return PJ_SUCCESS; + } else if ((cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) && + (stream->param.dir & PJMEDIA_DIR_PLAYBACK)) { + /* Playback volume */ + *(unsigned*)pval = stream->param.output_vol; + return PJ_SUCCESS; } else { return PJMEDIA_EAUD_INVCAP; } @@ -934,46 +1021,75 @@ /* API: set capability */ -static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm, +static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, const void *value) { - struct alsa_factory *af = ((struct alsa_stream*)strm)->af; + struct alsa_stream *stream = (struct alsa_stream*)s; - if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && - pj_ansi_strlen(af->pb_mixer_name)) + PJ_ASSERT_RETURN(s && value, PJ_EINVAL); + + /* only the output volume can be adjusted during runtime */ + if (cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { - pj_ssize_t min, max; - snd_mixer_t *handle; - snd_mixer_selem_id_t *sid; - snd_mixer_elem_t* elem; - unsigned vol = *(unsigned*)value; + struct alsa_factory *af = stream->af; + snd_pcm_info_t *info = NULL; + int card = -1; - if (snd_mixer_open(&handle, 0) < 0) - return PJMEDIA_EAUD_SYSERR; + /* get the corresponding card */ + if (snd_pcm_info_malloc(&info) == 0) { + if (snd_pcm_info(stream->pb_pcm, info) == 0) + card = snd_pcm_info_get_card(info); - if (snd_mixer_attach(handle, "default") < 0) - return PJMEDIA_EAUD_SYSERR; + snd_pcm_info_free(info); + } - if (snd_mixer_selem_register(handle, NULL, NULL) < 0) + /* card in range? */ + if ((card < 0) || (card >= af->card_cnt)) return PJMEDIA_EAUD_SYSERR; - if (snd_mixer_load(handle) < 0) - return PJMEDIA_EAUD_SYSERR; + /* does this card have a mixer? */ + if (pj_ansi_strlen(af->cards[card].pb_mixer_name)) + { + pj_ssize_t min, max; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t* elem; + unsigned vol = *(unsigned*)value; - snd_mixer_selem_id_alloca(&sid); - snd_mixer_selem_id_set_index(sid, 0); - snd_mixer_selem_id_set_name(sid, af->pb_mixer_name); - elem = snd_mixer_find_selem(handle, sid); - if (!elem) - return PJMEDIA_EAUD_SYSERR; + if (snd_mixer_open(&handle, 0) < 0) + return PJMEDIA_EAUD_SYSERR; - snd_mixer_selem_get_playback_volume_range(elem, &min, &max); - if (snd_mixer_selem_set_playback_volume_all(elem, vol * max / 100) < 0) - return PJMEDIA_EAUD_SYSERR; + if (snd_mixer_attach(handle, af->cards[card].hctl) < 0) + return PJMEDIA_EAUD_SYSERR; - snd_mixer_close(handle); - return PJ_SUCCESS; + if (snd_mixer_selem_register(handle, NULL, NULL) < 0) + return PJMEDIA_EAUD_SYSERR; + + if (snd_mixer_load(handle) < 0) + return PJMEDIA_EAUD_SYSERR; + + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, af->cards[card].pb_mixer_name); + elem = snd_mixer_find_selem(handle, sid); + if (!elem) + return PJMEDIA_EAUD_SYSERR; + + snd_mixer_selem_get_playback_volume_range(elem, &min, &max); + if (snd_mixer_selem_set_playback_volume_all(elem, + vol * max / 100) < 0) + return PJMEDIA_EAUD_SYSERR; + + /* store currently used value */ + stream->param.output_vol = vol; + + snd_mixer_close(handle); + return PJ_SUCCESS; + } + else + /* no mixer */ + return PJMEDIA_EAUD_INVOP; } return PJMEDIA_EAUD_INVCAP; @@ -986,6 +1102,8 @@ struct alsa_stream *stream = (struct alsa_stream*)s; pj_status_t status = PJ_SUCCESS; + PJ_ASSERT_RETURN(s, PJ_EINVAL); + stream->quit = 0; if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) { status = pj_thread_create (stream->pool, @@ -1024,6 +1142,8 @@ { struct alsa_stream *stream = (struct alsa_stream*)s; + PJ_ASSERT_RETURN(s, PJ_EINVAL); + stream->quit = 1; if (stream->pb_thread) { @@ -1054,11 +1174,13 @@ } - +/* API: destroy stream */ static pj_status_t alsa_stream_destroy (pjmedia_aud_stream *s) { struct alsa_stream *stream = (struct alsa_stream*)s; + PJ_ASSERT_RETURN(s, PJ_EINVAL); + alsa_stream_stop (s); if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {
_______________________________________________ Visit our blog: http://blog.pjsip.org pjsip mailing list pjsip@xxxxxxxxxxxxxxx http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org