Codebase list squeezelite / d1848c2
add support for using alsa mixer as volume control Adrian Smith 9 years ago
6 changed file(s) with 184 addition(s) and 8 deletion(s). Raw diff Collapse all Expand all
132132 Features
133133 - support for closing output device when idle with -C option
134134 - add support for basic IR input using LIRC on Linux
135 - add support for volume adjustment via alsa mixer
4545 " -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
4646 " -l \t\t\tList output devices\n"
4747 #if ALSA
48 " -L \t\t\tList volume controls for output device\n"
49 " -V <control>\t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n"
4850 " -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n"
4951 #endif
5052 #if PORTAUDIO
205207 #endif
206208 #if ALSA
207209 unsigned rt_priority = OUTPUT_RT_PRIORITY;
210 char *output_mixer = NULL;
208211 #endif
209212 #if DSD
210213 bool dop = false;
241244
242245 while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') {
243246 char *opt = argv[optind] + 1;
244 if (strstr("oabcCdefmMnNpPrs", opt) && optind < argc - 1) {
247 if (strstr("oabcCdefmMnNpPrs"
248 #if ALSA
249 "V"
250 #endif
251 , opt) && optind < argc - 1) {
245252 optarg = argv[optind + 1];
246253 optind += 2;
247254 } else if (strstr("ltz?"
255 #if ALSA
256 "L"
257 #endif
248258 #if RESAMPLE
249259 "uR"
250260 #endif
410420 list_devices();
411421 exit(0);
412422 break;
423 #if ALSA
424 case 'L':
425 list_mixers(output_device);
426 exit(0);
427 break;
428 #endif
413429 #if RESAMPLE
414430 case 'u':
415431 case 'R':
431447 #if VISEXPORT
432448 case 'v':
433449 visexport = true;
450 break;
451 #endif
452 #if ALSA
453 case 'V':
454 output_mixer = optarg;
434455 break;
435456 #endif
436457 #if IR
530551 output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay);
531552 } else {
532553 #if ALSA
533 output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle);
554 output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, output_mixer);
534555 #endif
535556 #if PORTAUDIO
536557 output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
2626 #include <alsa/asoundlib.h>
2727 #include <sys/mman.h>
2828 #include <malloc.h>
29 #include <math.h>
2930
3031 #define MAX_DEVICE_LEN 128
3132
4849 bool mmap;
4950 bool reopen;
5051 u8_t *write_buf;
52 const char *volume_mixer;
5153 } alsa;
5254
5355 static snd_pcm_t *pcmp = NULL;
9092 snd_device_name_free_hint(hints);
9193 }
9294 printf("\n");
95 }
96
97 void list_mixers(const char *output_device) {
98 int err;
99 snd_mixer_t *handle;
100 snd_mixer_selem_id_t *sid;
101 snd_mixer_elem_t *elem;
102 snd_mixer_selem_id_alloca(&sid);
103
104 LOG_INFO("listing mixers for: %s", output_device);
105
106 if ((err = snd_mixer_open(&handle, 0)) < 0) {
107 LOG_ERROR("open error: %s", snd_strerror(err));
108 return;
109 }
110 if ((err = snd_mixer_attach(handle, output_device)) < 0) {
111 LOG_ERROR("attach error: %s", snd_strerror(err));
112 snd_mixer_close(handle);
113 return;
114 }
115 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
116 LOG_ERROR("register error: %s", snd_strerror(err));
117 snd_mixer_close(handle);
118 return;
119 }
120 if ((err = snd_mixer_load(handle)) < 0) {
121 LOG_ERROR("load error: %s", snd_strerror(err));
122 snd_mixer_close(handle);
123 return;
124 }
125
126 printf("Volume controls for %s\n", output_device);
127 for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) {
128 if (snd_mixer_selem_has_playback_volume(elem)) {
129 snd_mixer_selem_get_id(elem, sid);
130 printf(" %s\n", snd_mixer_selem_id_get_name(sid));//, snd_mixer_selem_id_get_index(sid));
131 }
132 }
133 printf("\n");
134
135 snd_mixer_close(handle);
136 }
137
138 #define MINVOL_DB 72 // LMS volume map for SqueezePlay sends values in range ~ -72..0 dB
139
140 void set_volume(unsigned left, unsigned right) {
141 int err;
142 float ldB, rdB;
143 long nleft, nright;
144 long min, max;
145 snd_mixer_t *handle;
146 snd_mixer_selem_id_t *sid;
147 snd_mixer_elem_t* elem;
148
149 if (!alsa.volume_mixer) {
150 LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
151 LOCK;
152 output.gainL = left;
153 output.gainR = right;
154 UNLOCK;
155 return;
156 } else {
157 LOCK;
158 output.gainL = FIXED_ONE;
159 output.gainR = FIXED_ONE;
160 UNLOCK;
161 }
162
163 // convert 16.16 fixed point to dB
164 ldB = 20 * log10( left / 65536.0F );
165 rdB = 20 * log10( right / 65536.0F );
166
167 if ((err = snd_mixer_open(&handle, 0)) < 0) {
168 LOG_ERROR("open error: %s", snd_strerror(err));
169 return;
170 }
171 if ((err = snd_mixer_attach(handle, output.device)) < 0) {
172 LOG_ERROR("attach error: %s", snd_strerror(err));
173 snd_mixer_close(handle);
174 return;
175 }
176 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
177 LOG_ERROR("register error: %s", snd_strerror(err));
178 snd_mixer_close(handle);
179 return;
180 }
181 if ((err = snd_mixer_load(handle)) < 0) {
182 LOG_ERROR("load error: %s", snd_strerror(err));
183 snd_mixer_close(handle);
184 return;
185 }
186
187 snd_mixer_selem_id_alloca(&sid);
188
189 snd_mixer_selem_id_set_index(sid, 0);
190 snd_mixer_selem_id_set_name(sid, alsa.volume_mixer);
191
192 if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) {
193 LOG_ERROR("error find selem %s: %s", alsa.volume_mixer, snd_strerror(err));
194 snd_mixer_close(handle);
195 return;
196 }
197
198 if (snd_mixer_selem_has_playback_switch(elem)) {
199 snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute
200 }
201
202 if ((err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max)) < 0) {
203 // unable to get db range - set using raw values
204 if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) {
205 LOG_ERROR("unable to get volume raw", min, max);
206 } else {
207 long lraw = (MINVOL_DB + floor(ldB)) / MINVOL_DB * max;
208 long rraw = (MINVOL_DB + floor(rdB)) / MINVOL_DB * max;
209 LOG_DEBUG("setting vol raw [%ld-%ld]", min, max);
210 if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) {
211 LOG_ERROR("error setting left volume: %s", snd_strerror(err));
212 }
213 if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) {
214 LOG_ERROR("error setting right volume: %s", snd_strerror(err));
215 }
216 }
217 } else {
218 // set db directly
219 LOG_DEBUG("setting vol dB");
220 if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB, 1)) < 0) {
221 LOG_ERROR("error setting left volume: %s", snd_strerror(err));
222 }
223 if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB, 1)) < 0) {
224 LOG_ERROR("error setting right volume: %s", snd_strerror(err));
225 }
226 }
227
228 if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) {
229 LOG_ERROR("error getting left vol: %s", snd_strerror(err));
230 }
231 if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) {
232 LOG_ERROR("error getting right vol: %s", snd_strerror(err));
233 }
234
235 LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", alsa.volume_mixer, ldB, nleft, rdB, nright);
236
237 snd_mixer_close(handle);
93238 }
94239
95240 static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) {
618763 static pthread_t thread;
619764
620765 void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[],
621 unsigned rate_delay, unsigned rt_priority, unsigned idle) {
766 unsigned rate_delay, unsigned rt_priority, unsigned idle, const char *volume_mixer) {
622767
623768 unsigned alsa_buffer = ALSA_BUFFER_TIME;
624769 unsigned alsa_period = ALSA_PERIOD_COUNT;
648793 alsa.write_buf = NULL;
649794 alsa.format = 0;
650795 alsa.reopen = alsa_reopen;
796 alsa.volume_mixer = volume_mixer;
651797 output.format = 0;
652798 output.buffer = alsa_buffer;
653799 output.period = alsa_period;
7171 }
7272 }
7373
74 void set_volume(unsigned left, unsigned right) {
75 LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
76 LOCK;
77 output.gainL = left;
78 output.gainR = right;
79 UNLOCK;
80 }
81
7482 static int pa_device_id(const char *device) {
7583 int len = strlen(device);
7684 int i;
410410
411411 LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust);
412412
413 LOCK_O;
414 output.gainL = audg->adjust ? audg->gainL : FIXED_ONE;
415 output.gainR = audg->adjust ? audg->gainR : FIXED_ONE;
416 UNLOCK_O;
413 set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
417414 }
418415
419416 static void process_setd(u8_t *pkt, int len) {
590590 // output_alsa.c
591591 #if ALSA
592592 void list_devices(void);
593 void list_mixers(const char *output_device);
594 void set_volume(unsigned left, unsigned right);
593595 bool test_open(const char *device, unsigned rates[]);
594596 void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[],
595 unsigned rate_delay, unsigned rt_priority, unsigned idle);
597 unsigned rate_delay, unsigned rt_priority, unsigned idle, const char *volume_mixer);
596598 void output_close_alsa(void);
597599 #endif
598600
599601 // output_pa.c
600602 #if PORTAUDIO
601603 void list_devices(void);
604 void set_volume(unsigned left, unsigned right);
602605 bool test_open(const char *device, unsigned rates[]);
603606 void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
604607 void output_close_pa(void);