ALSA dev volume extensions (with patch)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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,
+		&param->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

[Index of Archives]     [Asterisk Users]     [Asterisk App Development]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [Linux API]
  Powered by Linux