142 | 142 |
|
143 | 143 |
#define MINVOL_DB 72 // LMS volume map for SqueezePlay sends values in range ~ -72..0 dB
|
144 | 144 |
|
145 | |
void set_volume(unsigned left, unsigned right) {
|
|
145 |
static void set_mixer(const char *device, const char *mixer, int mixer_index, bool setmax, float ldB, float rdB) {
|
146 | 146 |
int err;
|
147 | |
float ldB, rdB;
|
148 | 147 |
long nleft, nright;
|
149 | 148 |
long min, max;
|
150 | 149 |
snd_mixer_t *handle;
|
151 | 150 |
snd_mixer_selem_id_t *sid;
|
152 | 151 |
snd_mixer_elem_t* elem;
|
|
152 |
|
|
153 |
if ((err = snd_mixer_open(&handle, 0)) < 0) {
|
|
154 |
LOG_ERROR("open error: %s", snd_strerror(err));
|
|
155 |
return;
|
|
156 |
}
|
|
157 |
if ((err = snd_mixer_attach(handle, device)) < 0) {
|
|
158 |
LOG_ERROR("attach error: %s", snd_strerror(err));
|
|
159 |
snd_mixer_close(handle);
|
|
160 |
return;
|
|
161 |
}
|
|
162 |
if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
|
|
163 |
LOG_ERROR("register error: %s", snd_strerror(err));
|
|
164 |
snd_mixer_close(handle);
|
|
165 |
return;
|
|
166 |
}
|
|
167 |
if ((err = snd_mixer_load(handle)) < 0) {
|
|
168 |
LOG_ERROR("load error: %s", snd_strerror(err));
|
|
169 |
snd_mixer_close(handle);
|
|
170 |
return;
|
|
171 |
}
|
|
172 |
|
|
173 |
snd_mixer_selem_id_alloca(&sid);
|
|
174 |
|
|
175 |
snd_mixer_selem_id_set_index(sid, mixer_index);
|
|
176 |
snd_mixer_selem_id_set_name(sid, mixer);
|
|
177 |
|
|
178 |
if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) {
|
|
179 |
LOG_ERROR("error find selem %s", mixer);
|
|
180 |
snd_mixer_close(handle);
|
|
181 |
return;
|
|
182 |
}
|
|
183 |
|
|
184 |
if (snd_mixer_selem_has_playback_switch(elem)) {
|
|
185 |
snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute
|
|
186 |
}
|
|
187 |
|
|
188 |
err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max);
|
|
189 |
|
|
190 |
if (err < 0 || max - min < 1000 || setmax) {
|
|
191 |
// unable to get db range or range is less than 10dB - ignore and set using raw values
|
|
192 |
if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) {
|
|
193 |
LOG_ERROR("unable to get volume raw range");
|
|
194 |
} else {
|
|
195 |
long lraw, rraw;
|
|
196 |
if (setmax) {
|
|
197 |
lraw = rraw = max;
|
|
198 |
} else {
|
|
199 |
lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (max-min)) + min;
|
|
200 |
rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (max-min)) + min;
|
|
201 |
}
|
|
202 |
LOG_DEBUG("setting vol raw [%ld..%ld]", min, max);
|
|
203 |
if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) {
|
|
204 |
LOG_ERROR("error setting left volume: %s", snd_strerror(err));
|
|
205 |
}
|
|
206 |
if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) {
|
|
207 |
LOG_ERROR("error setting right volume: %s", snd_strerror(err));
|
|
208 |
}
|
|
209 |
}
|
|
210 |
} else {
|
|
211 |
// set db directly
|
|
212 |
LOG_DEBUG("setting vol dB [%ld..%ld]", min, max);
|
|
213 |
if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB + max, 1)) < 0) {
|
|
214 |
LOG_ERROR("error setting left volume: %s", snd_strerror(err));
|
|
215 |
}
|
|
216 |
if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB + max, 1)) < 0) {
|
|
217 |
LOG_ERROR("error setting right volume: %s", snd_strerror(err));
|
|
218 |
}
|
|
219 |
}
|
|
220 |
|
|
221 |
if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) {
|
|
222 |
LOG_ERROR("error getting left vol: %s", snd_strerror(err));
|
|
223 |
}
|
|
224 |
if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) {
|
|
225 |
LOG_ERROR("error getting right vol: %s", snd_strerror(err));
|
|
226 |
}
|
|
227 |
|
|
228 |
LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", mixer, ldB, nleft, rdB, nright);
|
|
229 |
|
|
230 |
snd_mixer_close(handle);
|
|
231 |
}
|
|
232 |
|
|
233 |
void set_volume(unsigned left, unsigned right) {
|
|
234 |
float ldB, rdB;
|
153 | 235 |
|
154 | 236 |
if (!alsa.volume_mixer_name) {
|
155 | 237 |
LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
|
|
169 | 251 |
ldB = 20 * log10( left / 65536.0F );
|
170 | 252 |
rdB = 20 * log10( right / 65536.0F );
|
171 | 253 |
|
172 | |
if ((err = snd_mixer_open(&handle, 0)) < 0) {
|
173 | |
LOG_ERROR("open error: %s", snd_strerror(err));
|
174 | |
return;
|
175 | |
}
|
176 | |
if ((err = snd_mixer_attach(handle, output.device)) < 0) {
|
177 | |
LOG_ERROR("attach error: %s", snd_strerror(err));
|
178 | |
snd_mixer_close(handle);
|
179 | |
return;
|
180 | |
}
|
181 | |
if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
|
182 | |
LOG_ERROR("register error: %s", snd_strerror(err));
|
183 | |
snd_mixer_close(handle);
|
184 | |
return;
|
185 | |
}
|
186 | |
if ((err = snd_mixer_load(handle)) < 0) {
|
187 | |
LOG_ERROR("load error: %s", snd_strerror(err));
|
188 | |
snd_mixer_close(handle);
|
189 | |
return;
|
190 | |
}
|
191 | |
|
192 | |
snd_mixer_selem_id_alloca(&sid);
|
193 | |
|
194 | |
snd_mixer_selem_id_set_index(sid, alsa.volume_mixer_index);
|
195 | |
snd_mixer_selem_id_set_name(sid, alsa.volume_mixer_name);
|
196 | |
|
197 | |
if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) {
|
198 | |
LOG_ERROR("error find selem %s", alsa.volume_mixer_name);
|
199 | |
snd_mixer_close(handle);
|
200 | |
return;
|
201 | |
}
|
202 | |
|
203 | |
if (snd_mixer_selem_has_playback_switch(elem)) {
|
204 | |
snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute
|
205 | |
}
|
206 | |
|
207 | |
if ((err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max)) < 0 || max - min < 1000) {
|
208 | |
// unable to get db range or range is less than 10dB - ignore and set using raw values
|
209 | |
if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) {
|
210 | |
LOG_ERROR("unable to get volume raw range");
|
211 | |
} else {
|
212 | |
long lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (max-min)) + min;
|
213 | |
long rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (max-min)) + min;
|
214 | |
LOG_DEBUG("setting vol raw [%ld..%ld]", min, max);
|
215 | |
if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) {
|
216 | |
LOG_ERROR("error setting left volume: %s", snd_strerror(err));
|
217 | |
}
|
218 | |
if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) {
|
219 | |
LOG_ERROR("error setting right volume: %s", snd_strerror(err));
|
220 | |
}
|
221 | |
}
|
222 | |
} else {
|
223 | |
// set db directly
|
224 | |
LOG_DEBUG("setting vol dB [%ld..%ld]", min, max);
|
225 | |
if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB + max, 1)) < 0) {
|
226 | |
LOG_ERROR("error setting left volume: %s", snd_strerror(err));
|
227 | |
}
|
228 | |
if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB + max, 1)) < 0) {
|
229 | |
LOG_ERROR("error setting right volume: %s", snd_strerror(err));
|
230 | |
}
|
231 | |
}
|
232 | |
|
233 | |
if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) {
|
234 | |
LOG_ERROR("error getting left vol: %s", snd_strerror(err));
|
235 | |
}
|
236 | |
if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) {
|
237 | |
LOG_ERROR("error getting right vol: %s", snd_strerror(err));
|
238 | |
}
|
239 | |
|
240 | |
LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", alsa.volume_mixer_name, ldB, nleft, rdB, nright);
|
241 | |
|
242 | |
snd_mixer_close(handle);
|
|
254 |
set_mixer(output.device, alsa.volume_mixer_name, alsa.volume_mixer_index, false, ldB, rdB);
|
243 | 255 |
}
|
244 | 256 |
|
245 | 257 |
static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) {
|
|
768 | 780 |
static pthread_t thread;
|
769 | 781 |
|
770 | 782 |
void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[],
|
771 | |
unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer) {
|
|
783 |
unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute) {
|
772 | 784 |
|
773 | 785 |
unsigned alsa_buffer = ALSA_BUFFER_TIME;
|
774 | 786 |
unsigned alsa_period = ALSA_PERIOD_COUNT;
|
|
801 | 813 |
alsa.write_buf = NULL;
|
802 | 814 |
alsa.format = 0;
|
803 | 815 |
alsa.reopen = alsa_reopen;
|
804 | |
alsa.volume_mixer_name = volume_mixer_name;
|
805 | |
alsa.volume_mixer_index = volume_mixer_index ? atoi(volume_mixer_index) : 0;
|
|
816 |
|
|
817 |
if (!mixer_unmute) {
|
|
818 |
alsa.volume_mixer_name = volume_mixer_name;
|
|
819 |
alsa.volume_mixer_index = volume_mixer_index ? atoi(volume_mixer_index) : 0;
|
|
820 |
}
|
|
821 |
|
806 | 822 |
output.format = 0;
|
807 | 823 |
output.buffer = alsa_buffer;
|
808 | 824 |
output.period = alsa_period;
|
|
823 | 839 |
snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler);
|
824 | 840 |
|
825 | 841 |
output_init_common(level, device, output_buf_size, rates, idle);
|
|
842 |
|
|
843 |
if (mixer_unmute && volume_mixer_name) {
|
|
844 |
set_mixer(output.device, volume_mixer_name, volume_mixer_index ? atoi(volume_mixer_index) : 0, true, 0, 0);
|
|
845 |
}
|
826 | 846 |
|
827 | 847 |
#if LINUX
|
828 | 848 |
// RT linux - aim to avoid pagefaults by locking memory:
|