1 | | #include <cstdio> |
2 | | #include <cstdlib> |
3 | | #include <sys/time.h> |
4 | | #include <time.h> |
5 | | #include "config.h" |
| 1 | /* |
| 2 | * Copyright (C) <=2008 unattributed author(s) |
| 3 | * Copyright (C) 2008 Alan Calvert |
| 4 | * |
| 5 | * This program is free software; you can redistribute it and/or |
| 6 | * modify it under the terms of the GNU General Public License |
| 7 | * as published by the Free Software Foundation; either version 2 |
| 8 | * of the License, or (at your option) any later version. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License |
| 16 | * along with this program; if not, write to the Free Software |
| 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 18 | * 02110-1301, USA. |
| 19 | */ |
28 | | // Set everything up |
| 33 | pcm.device = (audio_passthru) |
| 34 | ? QByteArray(audio_passthru_device.toAscii()) |
| 35 | : QByteArray(audio_main_device.toAscii()); |
| 36 | if (pcm.device.isEmpty()) |
| 37 | pcm.device = "default"; |
| 38 | pcm.handle = NULL; |
| 39 | pcm.logtag = "pcm '" + pcm.device + "' "; |
| 40 | pcm.sample_rate = settings.samplerate; |
| 41 | pcm.use_mmap = false; |
| 42 | |
| 43 | mixer.device = gContext->GetSetting("MixerDevice", "default"); |
| 44 | if (mixer.device.startsWith("ALSA:")) |
| 45 | mixer.device.remove(0, 5); |
| 46 | mixer.handle = NULL; |
| 47 | mixer.control = gContext->GetSetting("MixerControl", "PCM"); |
| 48 | mixer.elem = NULL; |
| 49 | mixer.logtag = "mixer '" + mixer.device + "' "; |
| 50 | |
39 | | snd_pcm_format_t format; |
40 | | unsigned int buffer_time, period_time; |
41 | | int err; |
42 | | |
43 | | if (pcm_handle != NULL) |
44 | | CloseDevice(); |
45 | | |
46 | | pcm_handle = NULL; |
47 | | numbadioctls = 0; |
48 | | |
49 | | QString real_device = (audio_passthru) ? |
50 | | audio_passthru_device : audio_main_device; |
51 | | |
52 | | VERBOSE(VB_GENERAL, QString("Opening ALSA audio device '%1'.") |
53 | | .arg(real_device)); |
54 | | |
55 | | QByteArray dev_ba = real_device.toLocal8Bit(); |
56 | | err = snd_pcm_open(&pcm_handle, dev_ba.constData(), |
57 | | SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); |
58 | | |
59 | | if (err < 0) |
60 | | { |
61 | | Error(QString("snd_pcm_open(%1): %2") |
62 | | .arg(real_device).arg(snd_strerror(err))); |
63 | | |
64 | | if (pcm_handle) |
65 | | CloseDevice(); |
| 61 | if (!PrepPCM()) |
67 | | } |
68 | | |
69 | | /* the audio fragment size was computed by using the next lower power of 2 |
70 | | of the following: |
71 | | |
72 | | const int video_frame_rate = 30; |
73 | | const int bits_per_byte = 8; |
74 | | int fbytes = (audio_bits * audio_channels * audio_samplerate) / |
75 | | (bits_per_byte * video_frame_rate); |
76 | | |
77 | | For telephony apps, a much shorter fragment size is needed to reduce the |
78 | | delay, and fragments should be multiples of the RTP packet size (10ms). |
79 | | 20ms delay should be the max introduced by the driver, which equates |
80 | | to 320 bytes at 8000 samples/sec and mono 16-bit samples |
81 | | */ |
82 | | if (source == AUDIOOUTPUT_TELEPHONY) |
83 | | { |
84 | | fragment_size = 320; |
85 | | buffer_time = 80000; // 80 ms |
86 | | period_time = buffer_time / 4; // 20ms |
87 | | } |
88 | | else |
| 63 | if (!PrepMixer()) |
90 | | fragment_size = 1536 * audio_channels * audio_bits / 8; |
91 | | period_time = 25000; // in usec, interrupt period time |
92 | | // in usec, for driver buffer alloc (64k max) |
93 | | buffer_time = period_time * 16; |
94 | | } |
95 | | |
96 | | if (audio_bits == 8) |
97 | | format = SND_PCM_FORMAT_S8; |
98 | | else if (audio_bits == 16) |
99 | | // is the sound data coming in really little-endian or is it |
100 | | // CPU-endian? |
101 | | #ifdef WORDS_BIGENDIAN |
102 | | format = SND_PCM_FORMAT_S16; |
103 | | #else |
104 | | format = SND_PCM_FORMAT_S16_LE; |
105 | | #endif |
106 | | else if (audio_bits == 24) |
107 | | #ifdef WORDS_BIGENDIAN |
108 | | format = SND_PCM_FORMAT_S24; |
109 | | #else |
110 | | format = SND_PCM_FORMAT_S24_LE; |
111 | | #endif |
112 | | else |
113 | | { |
114 | | Error(QString("Unknown sample format: %1 bits.").arg(audio_bits)); |
| 65 | CloseDevice(); |
126 | | } |
127 | | |
128 | | // make us think that soundcard buffer is 4 fragments smaller than |
129 | | // it really is |
130 | | audio_buffer_unused = soundcard_buffer_size - (fragment_size * 4); |
131 | | |
132 | | if (internal_vol) |
133 | | OpenMixer(set_initial_vol); |
134 | | |
135 | | // Device opened successfully |
| 72 | } |
| 73 | pcm.bytes_per_frame = snd_pcm_frames_to_bytes(pcm.handle, 1); |
| 74 | fragment_size = pcm.bytes_per_frame * pcm.period_size; |
| 75 | soundcard_buffer_size = pcm.bytes_per_frame * pcm.buffer_size; |
| 76 | VERBOSE(VB_AUDIO, LOC + pcm.logtag |
| 77 | + QString("duly opened: fragment size %1, " |
| 78 | "soundcard buffer size %2 (in bytes)") |
| 79 | .arg(fragment_size).arg(soundcard_buffer_size)); |
170 | | lw = pcm_write_func(pcm_handle, tmpbuf, frames); |
171 | | |
172 | | if (lw >= 0) |
173 | | { |
174 | | if (lw < frames) |
175 | | VERBOSE(VB_AUDIO, QString("WriteAudio: short write %1 bytes (ok)") |
176 | | .arg(lw * audio_bytes_per_sample)); |
177 | | |
178 | | frames -= lw; |
179 | | tmpbuf += lw * audio_bytes_per_sample; // bytes |
180 | | } |
181 | | else if (lw == -EAGAIN) |
182 | | { |
183 | | VERBOSE(VB_AUDIO, QString("WriteAudio: device is blocked - waiting")); |
184 | | |
185 | | snd_pcm_wait(pcm_handle, 10); |
186 | | } |
187 | | else if (lw == -EPIPE && |
188 | | snd_pcm_state(pcm_handle) == SND_PCM_STATE_XRUN) |
189 | | { |
190 | | VERBOSE(VB_IMPORTANT, "WriteAudio: buffer underrun"); |
191 | | |
192 | | if ((lw = snd_pcm_prepare(pcm_handle)) < 0) |
193 | | { |
194 | | Error(QString("WriteAudio: unable to recover from xrun: %1") |
195 | | .arg(snd_strerror(lw))); |
196 | | return; |
197 | | } |
198 | | } |
199 | | else if (lw == -ESTRPIPE) |
200 | | { |
201 | | VERBOSE(VB_IMPORTANT, "WriteAudio: device is suspended"); |
202 | | |
203 | | while ((lw = snd_pcm_resume(pcm_handle)) == -EAGAIN) |
204 | | usleep(200); |
205 | | |
206 | | if (lw < 0) |
207 | | { |
208 | | VERBOSE(VB_IMPORTANT, "WriteAudio: resume failed"); |
209 | | |
210 | | if ((lw = snd_pcm_prepare(pcm_handle)) < 0) |
211 | | { |
212 | | Error(QString("WriteAudio: unable to recover from suspend: %1") |
213 | | .arg(snd_strerror(lw))); |
214 | | return; |
215 | | } |
216 | | } |
217 | | } |
218 | | else if (lw == -EBADFD) |
219 | | { |
220 | | VERBOSE(VB_IMPORTANT, |
221 | | QString("WriteAudio: device is in a bad state (state = %1)") |
222 | | .arg(snd_pcm_state(pcm_handle))); |
223 | | return; |
224 | | } |
225 | | else |
226 | | { |
227 | | VERBOSE(VB_IMPORTANT, QString("pcm_write_func: %1 (%2)") |
228 | | .arg(snd_strerror(lw)).arg(lw)); |
229 | | VERBOSE(VB_IMPORTANT, QString("WriteAudio: snd_pcm_state == %1") |
230 | | .arg(snd_pcm_state(pcm_handle))); |
231 | | |
232 | | // CloseDevice(); |
233 | | return; |
234 | | } |
| 102 | case SND_PCM_STATE_XRUN: |
| 103 | case SND_PCM_STATE_SUSPENDED: |
| 104 | if (!XrunRecovery()) |
| 105 | break; |
| 106 | case SND_PCM_STATE_PREPARED: |
| 107 | if (AlsaBad(snd_pcm_start(pcm.handle), "pcm start failed")) |
| 108 | break; |
| 109 | case SND_PCM_STATE_RUNNING: |
| 110 | if (pcm.use_mmap) |
| 111 | WriteMmap(aubuf, size / pcm.bytes_per_frame); |
| 112 | else |
| 113 | WriteRw(aubuf, size / pcm.bytes_per_frame); |
| 114 | break; |
| 115 | |
| 116 | default: |
| 117 | VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag |
| 118 | + QString("alarming SND_PCM_STATE %1 through WriteAudio()") |
| 119 | .arg(state)); |
| 120 | break; |
275 | | } |
276 | | |
277 | | snd_pcm_sframes_t avail, delay; |
278 | | |
279 | | snd_pcm_state_t state = snd_pcm_state(pcm_handle); |
280 | | if (state == SND_PCM_STATE_RUNNING || |
281 | | state == SND_PCM_STATE_DRAINING) |
282 | | { |
283 | | snd_pcm_delay(pcm_handle, &delay); |
284 | | } |
285 | | |
286 | | avail = snd_pcm_avail_update(pcm_handle); |
287 | | if (avail < 0 || |
288 | | (snd_pcm_uframes_t)avail > (snd_pcm_uframes_t)soundcard_buffer_size) |
289 | | avail = soundcard_buffer_size; |
290 | | |
291 | | int space = (avail * audio_bytes_per_sample) - audio_buffer_unused; |
292 | | |
293 | | if (space < 0) |
294 | | space = 0; |
299 | | |
300 | | int AudioOutputALSA::SetParameters(snd_pcm_t *handle, |
301 | | snd_pcm_format_t format, unsigned int channels, |
302 | | unsigned int rate, unsigned int buffer_time, |
303 | | unsigned int period_time) |
304 | | { |
305 | | int err, dir; |
306 | | snd_pcm_hw_params_t *params; |
307 | | snd_pcm_sw_params_t *swparams; |
308 | | snd_pcm_uframes_t buffer_size; |
309 | | snd_pcm_uframes_t period_size; |
310 | | |
311 | | VERBOSE(VB_AUDIO, QString("in SetParameters(format=%1, channels=%2, " |
312 | | "rate=%3, buffer_time=%4, period_time=%5)") |
313 | | .arg(format).arg(channels).arg(rate).arg(buffer_time).arg(period_time)); |
314 | | |
315 | | if (handle == NULL) |
316 | | { |
317 | | VERBOSE(VB_IMPORTANT, QString("SetParameters() called with handle == NULL!")); |
| 156 | int AudioOutputALSA::GetVolumeChannel(int channel) const |
| 157 | { |
| 158 | if (mixer.elem == NULL) |
338 | | VERBOSE(VB_GENERAL, LOC_WARN + |
339 | | "mmap not available, attempting to fall back to slow writes."); |
340 | | QString old_err = snd_strerror(err); |
341 | | pcm_write_func = &snd_pcm_writei; |
342 | | err = snd_pcm_hw_params_set_access( |
343 | | handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); |
344 | | if (err < 0) |
345 | | { |
346 | | Error("Interleaved sound types MMAP & RW are not available"); |
347 | | VERBOSE(VB_IMPORTANT, |
348 | | QString("MMAP Error: %1\n\t\t\tRW Error: %2") |
349 | | .arg(old_err).arg(snd_strerror(err))); |
350 | | return err; |
| 164 | int chk; |
| 165 | snd_mixer_selem_channel_id_t chx = (snd_mixer_selem_channel_id_t)channel; |
| 166 | long mixvol; |
| 167 | chk = snd_mixer_selem_get_playback_volume(mixer.elem, chx, &mixvol); |
| 168 | if (chk < 0) |
| 169 | VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag |
| 170 | + QString("failed to get channel %2 volume: %3") |
| 171 | .arg(channel).arg(snd_strerror(chk))); |
| 172 | else if (mixer.volrange > 0L) |
| 173 | { |
| 174 | retvol = (mixvol - mixer.volmin) * 100.0f / mixer.volrange + .5f; |
| 175 | retvol = max(retvol, 0); |
| 176 | retvol = min(retvol, 100); |
| 177 | VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag |
| 178 | + QString("get volume channel %1: %2 (mixer volume: %3") |
| 179 | .arg(channel).arg(retvol).arg(mixvol)); |
365 | | Error(QString("Channels count (%1) not available: %2") |
366 | | .arg(channels).arg(snd_strerror(err))); |
367 | | return err; |
| 195 | snd_mixer_selem_channel_id_t chx = |
| 196 | (snd_mixer_selem_channel_id_t)channel; |
| 197 | long mixvol = volume * mixer.volrange / 100.0f - mixer.volmin + 0.5f; |
| 198 | mixvol = max(mixvol, mixer.volmin); |
| 199 | mixvol = min(mixvol, mixer.volmax); |
| 200 | if (!AlsaBad(snd_mixer_selem_set_playback_volume(mixer.elem, chx, mixvol), |
| 201 | QString("failed to set channel %1 volume -> %2") |
| 202 | .arg(channel).arg(volume))) |
| 203 | VERBOSE(VB_AUDIO, LOC + mixer.logtag |
| 204 | + QString("set channel %1 volume -> %2 (mixer volume: %3)") |
| 205 | .arg(channel).arg(volume).arg(mixvol)); |
386 | | /* set the buffer time */ |
387 | | if ((err = snd_pcm_hw_params_set_buffer_time_near(handle, params, |
388 | | &buffer_time, &dir)) < 0) |
389 | | { |
390 | | Error(QString("Unable to set buffer time %1 for playback: %2") |
391 | | .arg(buffer_time).arg(snd_strerror(err))); |
392 | | return err; |
| 228 | if (paused && !audio_actually_paused) |
| 229 | { |
| 230 | AlsaBad(snd_pcm_drop(pcm.handle), "pause drop failed"); |
| 231 | pauseaudio = paused; |
| 232 | audio_actually_paused = true; |
| 233 | VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "actually paused"); |
| 234 | } |
| 235 | else if (!paused && audio_actually_paused) |
| 236 | { |
| 237 | QString tag = "(un)Pause "; |
| 238 | AlsaBad(snd_pcm_drop(pcm.handle), tag + "unpause drop failed"); |
| 239 | AlsaBad(snd_pcm_prepare(pcm.handle), tag + "unpause prepare failed"); |
| 240 | pauseaudio = paused; |
| 241 | audio_actually_paused = false; |
| 242 | VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "actually unpaused"); |
395 | | if ((err = snd_pcm_hw_params_get_buffer_size(params, &buffer_size)) < 0) |
396 | | { |
397 | | Error(QString("Unable to get buffer size for playback: %1") |
398 | | .arg(snd_strerror(err))); |
399 | | return err; |
400 | | } else { |
401 | | VERBOSE(VB_AUDIO, QString("get_buffer_size returned %1").arg(buffer_size)); |
| 246 | void AudioOutputALSA::Drain(void) |
| 247 | { |
| 248 | AudioOutputBase::Drain(); |
| 249 | if (pcm.handle != NULL) |
| 250 | AlsaBad(snd_pcm_drain(pcm.handle), "pcm drain failed"); |
| 251 | } |
| 252 | |
| 253 | bool AudioOutputALSA::PrepPCM() |
| 254 | { |
| 255 | if (pcm.handle != NULL) |
| 256 | snd_pcm_close(pcm.handle); |
| 257 | AlsaBad(snd_config_update_free_global(), "failed to update snd config"); |
| 258 | QRegExp built_in_regx("^(front|surround\\d{2}|hdmi|rear|center_lfe|iec958)" |
| 259 | ":(CARD=\\w+,DEV=\\d)|(DEV=\\d,CARD=\\w+)"); |
| 260 | bool is_built_in = QString(pcm.device).contains(built_in_regx); |
| 261 | QRegExp plug_regx("^(plug|plughw):\\w+"); |
| 262 | bool is_plug = QString(pcm.device).contains(plug_regx); |
| 263 | |
| 264 | while (true) |
| 265 | { |
| 266 | pcm.logtag = "pcm '" + pcm.device + "' "; |
| 267 | if (AlsaBad(snd_pcm_open(&pcm.handle, pcm.device.constData(), |
| 268 | SND_PCM_STREAM_PLAYBACK, |
| 269 | SND_PCM_NO_AUTO_CHANNELS), |
| 270 | "failed to open device")) |
| 271 | { |
| 272 | pcm.handle = NULL; |
| 273 | return false; |
| 274 | } |
| 275 | if (AlsaBad(snd_pcm_nonblock(pcm.handle, 1), "set nonblock failed")) |
| 276 | { |
| 277 | snd_pcm_close(pcm.handle); |
| 278 | pcm.handle = NULL; |
| 279 | return false; |
| 280 | } |
| 281 | if (PrepHwparams()) |
| 282 | break; |
| 283 | else if (!is_plug && !is_built_in) // try again with 'plug:' |
| 284 | { |
| 285 | AlsaBad(snd_pcm_close(pcm.handle), "plug retry, close failed"); |
| 286 | pcm.handle = NULL; |
| 287 | pcm.device.prepend("plug:"); |
| 288 | is_plug = true; |
| 289 | continue; |
| 290 | } |
| 291 | else |
| 292 | { |
| 293 | snd_pcm_close(pcm.handle); |
| 294 | pcm.handle = NULL; |
| 295 | return false; |
| 296 | } |
409 | | Error(QString("Unable to set period time %1 for playback: %2") |
410 | | .arg(period_time).arg(snd_strerror(err))); |
411 | | return err; |
412 | | } else { |
413 | | VERBOSE(VB_AUDIO, QString("set_period_time_near returned %1").arg(period_time)); |
414 | | } |
415 | | |
416 | | if ((err = snd_pcm_hw_params_get_period_size(params, &period_size, |
417 | | &dir)) < 0) { |
418 | | Error(QString("Unable to get period size for playback: %1") |
419 | | .arg(snd_strerror(err))); |
420 | | return err; |
421 | | } else { |
422 | | VERBOSE(VB_AUDIO, QString("get_period_size returned %1").arg(period_size)); |
| 300 | snd_pcm_close(pcm.handle); |
| 301 | return false; |
425 | | /* write the parameters to device */ |
426 | | if ((err = snd_pcm_hw_params(handle, params)) < 0) { |
427 | | Error(QString("Unable to set hw params for playback: %1") |
428 | | .arg(snd_strerror(err))); |
429 | | return err; |
430 | | } |
431 | | |
432 | | /* get the current swparams */ |
433 | | if ((err = snd_pcm_sw_params_current(handle, swparams)) < 0) |
434 | | { |
435 | | Error(QString("Unable to determine current swparams for playback:" |
436 | | " %1").arg(snd_strerror(err))); |
437 | | return err; |
438 | | } |
439 | | /* start the transfer after period_size */ |
440 | | if ((err = snd_pcm_sw_params_set_start_threshold(handle, swparams, |
441 | | period_size)) < 0) |
442 | | { |
443 | | Error(QString("Unable to set start threshold mode for playback: %1") |
444 | | .arg(snd_strerror(err))); |
445 | | return err; |
446 | | } |
| 306 | bool AudioOutputALSA::PrepHwparams(void) |
| 307 | { |
| 308 | bool result = false; |
| 309 | snd_pcm_hw_params_t* hwparams; |
| 310 | snd_pcm_hw_params_alloca(&hwparams); |
| 311 | snd_pcm_access_t axs; |
| 312 | snd_pcm_format_t format; |
| 313 | unsigned int rate, latency, period_time, minchan, maxchan, periods; |
448 | | /* allow the transfer when at least period_size samples can be processed */ |
449 | | if ((err = snd_pcm_sw_params_set_avail_min(handle, swparams, |
450 | | period_size)) < 0) |
| 315 | if (AlsaBad(snd_pcm_hw_params_any(pcm.handle, hwparams), |
| 316 | "no playback configurations available")) |
| 317 | goto fini; |
| 318 | if (AlsaBad(snd_pcm_hw_params_set_periods_integer(pcm.handle, hwparams), |
| 319 | "cannot restrict period size to integral value")) |
| 320 | goto fini; |
| 321 | pcm.use_mmap = false; |
| 322 | axs = SND_PCM_ACCESS_MMAP_INTERLEAVED; |
| 323 | if (!AlsaBad(snd_pcm_hw_params_set_access(pcm.handle, hwparams, axs), |
| 324 | "mmap io access not possible")) |
| 325 | pcm.use_mmap = true; |
| 326 | else |
452 | | Error(QString("Unable to set avail min for playback: %1") |
453 | | .arg(snd_strerror(err))); |
454 | | return err; |
| 328 | axs = SND_PCM_ACCESS_RW_INTERLEAVED; |
| 329 | if (AlsaBad(snd_pcm_hw_params_set_access(pcm.handle, hwparams, axs), |
| 330 | "failed to set mmap or rw access, both failed")) |
| 331 | goto fini; |
| 332 | } |
| 333 | switch (audio_bits) // ALSA to append _LE/_BE accordingly |
| 334 | { |
| 335 | case 8: |
| 336 | format = SND_PCM_FORMAT_U8; |
| 337 | break; |
| 338 | case 16: |
| 339 | default: |
| 340 | format = SND_PCM_FORMAT_S16; |
| 341 | break; |
| 342 | } |
| 343 | if (AlsaBad(snd_pcm_hw_params_set_format(pcm.handle, hwparams, format), |
| 344 | "failed to set sample format")) |
| 345 | goto fini; |
| 346 | |
| 347 | rate = pcm.sample_rate; |
| 348 | if (AlsaBad(snd_pcm_hw_params_set_rate_near(pcm.handle, hwparams, &rate, NULL), |
| 349 | QString("failed setting sample rate near %1").arg(rate))) |
| 350 | goto fini; |
| 351 | VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag |
| 352 | + QString("requested sample rate %1, set sample rate %2") |
| 353 | .arg(pcm.sample_rate).arg(rate)); |
| 354 | if (rate < pcm.sample_rate * 0.95f || rate > pcm.sample_rate * 1.05f) |
| 355 | { |
| 356 | AlsaBad(-EINVAL, QString("requested sample rate %1, set sample rate %2 " |
| 357 | "- more than 5% off") |
| 358 | .arg(pcm.sample_rate).arg(rate)); |
| 359 | goto fini; |
| 360 | } |
| 361 | if (AlsaBad(snd_pcm_hw_params_set_channels(pcm.handle, hwparams, |
| 362 | audio_channels), |
| 363 | QString("failed to set channels to %1").arg(audio_channels))) |
| 364 | goto fini; |
| 365 | |
| 366 | // latency, period, buffer prep based on Alsa fn() snd_pcm_set_params() |
| 367 | latency = 50000; // 50msec |
| 368 | if (!AlsaBad(snd_pcm_hw_params_set_buffer_time_near(pcm.handle, hwparams, |
| 369 | &latency, NULL), |
| 370 | "initial buffer time/latency setting failed")) |
| 371 | { |
| 372 | if (AlsaBad(snd_pcm_hw_params_get_buffer_size(hwparams, &pcm.buffer_size), |
| 373 | "failed to get buffer size")) |
| 374 | goto fini; |
| 375 | if (AlsaBad(snd_pcm_hw_params_get_buffer_time(hwparams, &latency, NULL), |
| 376 | "failed to get latency setting")) |
| 377 | goto fini; |
| 378 | period_time = latency / 4; |
| 379 | if (AlsaBad(snd_pcm_hw_params_set_period_time_near(pcm.handle, hwparams, |
| 380 | &period_time, NULL), |
| 381 | "failed to set period time")) |
| 382 | goto fini; |
| 383 | if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &pcm.period_size, |
| 384 | NULL), |
| 385 | "failed to get period size")) |
| 386 | goto fini; |
460 | | Error(QString("Unable to set transfer align for playback: %1") |
461 | | .arg(snd_strerror(err))); |
462 | | return err; |
463 | | } |
| 390 | VERBOSE(VB_AUDIO, LOC + "hwparams settings, Plan B"); |
| 391 | period_time = latency / 4; |
| 392 | if (AlsaBad(snd_pcm_hw_params_set_period_time_near(pcm.handle, hwparams, |
| 393 | &period_time, NULL), |
| 394 | "failed to set period time")) |
| 395 | goto fini; |
| 396 | if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &pcm.period_size, |
| 397 | NULL), |
| 398 | "failed to get period size")) |
| 399 | goto fini; |
| 400 | pcm.buffer_size = pcm.period_size * 4; |
| 401 | if (AlsaBad(snd_pcm_hw_params_set_buffer_size_near(pcm.handle, hwparams, |
| 402 | &pcm.buffer_size), |
| 403 | "failed to set buffer size")) |
| 404 | goto fini; |
| 405 | if (AlsaBad(snd_pcm_hw_params_get_buffer_size(hwparams, |
| 406 | &pcm.buffer_size), |
| 407 | "failed to get buffer size")) |
| 408 | goto fini; |
| 409 | } |
| 410 | if (AlsaBad(snd_pcm_hw_params (pcm.handle, hwparams), |
| 411 | "failed to set hardware parameters")) |
| 412 | goto fini; |
| 413 | |
| 414 | result = true; |
| 415 | periods = 0; |
| 416 | AlsaBad(snd_pcm_hw_params_get_periods(hwparams, &periods, NULL), |
| 417 | "failed to get periods"); |
| 418 | VERBOSE(VB_AUDIO, LOC + pcm.logtag |
| 419 | + QString("latency %1msec, period size %2, periods %3, " |
| 420 | "buffer size %4 (in frames)") |
| 421 | .arg(latency /1000.0, 0, 'f', 1).arg(pcm.period_size) |
| 422 | .arg(periods).arg(pcm.buffer_size)); |
| 423 | VERBOSE(VB_AUDIO, LOC + pcm.logtag |
| 424 | + QString("channels %1, using %2").arg(audio_channels) |
| 425 | .arg((pcm.use_mmap) ? "mmap write" : "snd_pcm_writei")); |
| 426 | fini: |
| 427 | return result; |
| 428 | } |
473 | | if ((err = snd_pcm_prepare(handle)) < 0) |
474 | | Error(QString("Initial pcm prepare err %1 %2") |
475 | | .arg(err).arg(snd_strerror(err))); |
| 437 | if (AlsaBad(snd_pcm_sw_params_current(pcm.handle, swparams), |
| 438 | "failed to get swparams")) |
| 439 | goto fini; |
| 440 | |
| 441 | // using explicit start, not auto start |
| 442 | if (AlsaBad(snd_pcm_sw_params_set_start_threshold(pcm.handle, swparams, |
| 443 | INT_MAX), |
| 444 | "failed to set start threshold")) |
| 445 | goto fini; |
| 446 | if (AlsaBad(snd_pcm_sw_params_get_boundary(swparams, &boundary), |
| 447 | "failed to get boundary")) |
| 448 | goto fini; |
| 449 | if (AlsaBad(snd_pcm_sw_params_set_stop_threshold(pcm.handle, swparams, |
| 450 | boundary), |
| 451 | "failed to set stop threshold")) |
| 452 | goto fini; |
| 453 | if (AlsaBad(snd_pcm_sw_params(pcm.handle, swparams), |
| 454 | "failed to set software parameters")) |
| 455 | goto fini; |
| 456 | |
| 457 | result = true; |
501 | | |
502 | | snd_mixer_selem_channel_id_t chan = (snd_mixer_selem_channel_id_t) channel; |
503 | | if (!snd_mixer_selem_has_playback_channel(elem, chan)) |
| 475 | struct snd_mixer_selem_regopt regopts = |
| 476 | {1, SND_MIXER_SABSTRACT_NONE, mixer.device.constData(), NULL, NULL}; |
| 477 | if ((chk = snd_mixer_selem_register(mixer.handle, ®opts, NULL)) < 0) |
| 478 | { |
| 479 | VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag |
| 480 | + QString("failed to register: %2").arg(snd_strerror(chk))); |
| 481 | snd_mixer_close(mixer.handle); |
| 482 | mixer.handle = NULL; |
| 483 | return false; |
| 484 | } |
| 485 | if ((chk = snd_mixer_load(mixer.handle)) < 0) |
513 | | |
514 | | ALSAVolumeInfo vinfo = GetVolumeRange(elem); |
515 | | |
516 | | snd_mixer_selem_get_playback_volume( |
517 | | elem, (snd_mixer_selem_channel_id_t)channel, &actual_volume); |
518 | | |
519 | | return vinfo.ToMythRange(actual_volume); |
520 | | } |
521 | | |
522 | | void AudioOutputALSA::SetVolumeChannel(int channel, int volume) |
523 | | { |
524 | | SetCurrentVolume(mixer_control, channel, volume); |
525 | | } |
526 | | |
527 | | void AudioOutputALSA::SetCurrentVolume(QString control, int channel, int volume) |
528 | | { |
529 | | VERBOSE(VB_AUDIO, QString("Setting %1 volume to %2") |
530 | | .arg(control).arg(volume)); |
531 | | |
532 | | if (!mixer_handle) |
533 | | return; // no mixer, nothing to do |
534 | | |
535 | | QByteArray ctl = control.toAscii(); |
536 | | snd_mixer_selem_id_t *sid; |
537 | | snd_mixer_selem_id_alloca(&sid); |
538 | | snd_mixer_selem_id_set_index(sid, 0); |
539 | | snd_mixer_selem_id_set_name(sid, ctl.constData()); |
540 | | |
541 | | snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer_handle, sid); |
542 | | if (!elem) |
| 493 | unsigned int elcount = snd_mixer_get_count(mixer.handle); |
| 494 | snd_mixer_elem_t* elx = snd_mixer_first_elem(mixer.handle); |
| 495 | mixer.elem = NULL; |
| 496 | for (unsigned int ctr = 0; elx != NULL && ctr < elcount; ++ctr) |
| 497 | { |
| 498 | if (!strcmp(mixer.control.constData(), snd_mixer_selem_get_name(elx)) |
| 499 | && !snd_mixer_selem_is_enumerated(elx) |
| 500 | && snd_mixer_selem_has_playback_volume(elx) |
| 501 | && snd_mixer_selem_is_active(elx)) |
| 502 | { |
| 503 | mixer.elem = elx; |
| 504 | mixer.logtag += "control '" + mixer.control + "', "; |
| 505 | VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag |
| 506 | + QString("playback control '%1' selected") |
| 507 | .arg(mixer.control.constData())); |
| 508 | break; |
| 509 | } |
| 510 | elx = snd_mixer_elem_next(elx); |
| 511 | } |
| 512 | if (mixer.elem == NULL) |
| 513 | { |
| 514 | VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag |
| 515 | + QString("playback control '%1' not found") |
| 516 | .arg(mixer.control.constData())); |
| 517 | snd_mixer_close(mixer.handle); |
| 518 | mixer.handle = NULL; |
| 519 | return false; |
| 520 | } |
| 521 | if (AlsaBad(snd_mixer_selem_get_playback_volume_range(mixer.elem, |
| 522 | &mixer.volmin, |
| 523 | &mixer.volmax), |
| 524 | "failed to get volume range")) |
| 530 | mixer.volrange = mixer.volmax - mixer.volmin; |
| 531 | VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag |
| 532 | + QString("volume range - min %1, max %2, range %3") |
| 533 | .arg(mixer.volmin).arg(mixer.volmax).arg(mixer.volrange)); |
| 534 | if (set_initial_vol) |
| 535 | { |
| 536 | int initial_vol; |
| 537 | if ( mixer.control == "PCM") |
| 538 | initial_vol = gContext->GetNumSetting("PCMMixerVolume", 80); |
| 539 | else |
| 540 | initial_vol = gContext->GetNumSetting("MasterMixerVolume", 80); |
| 541 | for (int ch = 0; ch < audio_channels; ++ch) |
| 542 | SetVolumeChannel(ch, initial_vol); |
| 543 | } |
| 544 | VERBOSE(VB_AUDIO, LOC + mixer.logtag + "set up ok"); |
| 545 | return true; |
| 546 | } |
549 | | snd_mixer_selem_channel_id_t chan = (snd_mixer_selem_channel_id_t) channel; |
550 | | if (!snd_mixer_selem_has_playback_channel(elem, chan)) |
551 | | { |
552 | | snd_mixer_selem_id_set_index(sid, channel); |
553 | | if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL) |
| 548 | void AudioOutputALSA::WriteMmap(unsigned char *data, snd_pcm_uframes_t nframes) |
| 549 | { |
| 550 | unsigned char* ptr8 = data; |
| 551 | int16_t* ptr16 = (int16_t*)data; |
| 552 | int retries = 0; |
| 553 | int chk; |
| 554 | while ( nframes > 0 && retries < 3) |
| 555 | { |
| 556 | snd_pcm_hwsync(pcm.handle); |
| 557 | snd_pcm_sframes_t avail = snd_pcm_avail_update(pcm.handle); |
| 558 | if (avail < 0) |
| 559 | { |
| 560 | VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag |
| 561 | + "WriteMmap failed to get avail"); |
| 562 | if (Recovery(avail)) |
| 563 | { |
| 564 | ++retries; |
| 565 | continue; |
| 566 | } |
| 567 | else |
| 568 | return; |
| 569 | } |
| 570 | snd_pcm_uframes_t towrite = |
| 571 | (nframes > (snd_pcm_uframes_t)avail) ? avail : nframes; |
| 572 | const snd_pcm_channel_area_t* areas; |
| 573 | snd_pcm_uframes_t offset; |
| 574 | if ((chk = snd_pcm_mmap_begin(pcm.handle, &areas, &offset, &towrite)) |
| 575 | < 0) |
| 581 | // get channel bases |
| 582 | unsigned char* samples[audio_channels]; |
| 583 | int steps[audio_channels]; |
| 584 | int ch; |
| 585 | for (ch = 0; ch < audio_channels; ++ch) |
| 586 | { |
| 587 | samples[ch] = (unsigned char*)areas[ch].addr |
| 588 | + (areas[ch].first + areas[ch].step * offset ) / 8; |
| 589 | steps[ch] = areas[ch].step / 8; |
| 590 | } |
| 591 | // wrrrrite ... |
| 592 | unsigned int frm; |
| 593 | switch( audio_bits) |
| 594 | { |
| 595 | case 16: |
| 596 | for (frm = towrite; frm; --frm) |
| 597 | for(ch = 0; ch < audio_channels; ++ch) |
| 598 | { |
| 599 | *(int16_t*)(samples[ch]) = *ptr16++; |
| 600 | samples[ch] += steps[ch]; |
| 601 | } |
| 602 | break; |
| 603 | |
| 604 | case 8: |
| 605 | for (frm = towrite; frm; --frm) |
| 606 | for(ch = 0; ch < audio_channels; ++ch) |
| 607 | { |
| 608 | *samples[ch] = *ptr8++; |
| 609 | samples[ch] += steps[ch]; |
| 610 | } |
| 611 | break; |
| 612 | default: |
| 613 | VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag |
| 614 | + "invalid sample format, DirectWrite"); |
| 615 | return; |
| 616 | } |
| 617 | chk = snd_pcm_mmap_commit(pcm.handle, offset, towrite); |
| 618 | if (chk < 0) |
| 619 | { |
| 620 | VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag |
| 621 | + "mmap commit failed"); |
| 622 | return; |
| 623 | } |
| 624 | if (chk < (int)towrite) |
| 625 | VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag |
| 626 | + QString("short write, %1 / %2, DirectWrite") |
| 627 | .arg(chk).arg(towrite)); |
| 628 | nframes -= towrite; |
| 629 | ++retries; |
562 | | ALSAVolumeInfo vinfo = GetVolumeRange(elem); |
563 | | |
564 | | long set_vol = vinfo.ToALSARange(volume); |
565 | | |
566 | | int err = snd_mixer_selem_set_playback_volume(elem, chan, set_vol); |
567 | | if (err < 0) |
568 | | { |
569 | | VERBOSE(VB_IMPORTANT, QString("mixer set channel %1 err %2: %3") |
570 | | .arg(channel).arg(err).arg(snd_strerror(err))); |
571 | | } |
572 | | else |
573 | | { |
574 | | VERBOSE(VB_AUDIO, QString("channel %1 vol set to %2") |
575 | | .arg(channel).arg(set_vol)); |
576 | | } |
577 | | |
578 | | if (snd_mixer_selem_has_playback_switch(elem)) |
| 633 | void AudioOutputALSA::WriteRw(unsigned char *data, snd_pcm_uframes_t nframes) |
| 634 | { |
| 635 | snd_pcm_uframes_t towrite; |
| 636 | snd_pcm_sframes_t wrote, avail; |
| 637 | int retries = 0; |
| 638 | for (wrote = 0; nframes > 0 && retries < 3; data += wrote * pcm.bytes_per_frame) |
617 | | volume = gContext->GetNumSetting("MasterMixerVolume", 80); |
618 | | SetCurrentVolume("Master", 0, volume); |
619 | | SetCurrentVolume("Master", 1, volume); |
620 | | |
621 | | volume = gContext->GetNumSetting("PCMMixerVolume", 80); |
622 | | SetCurrentVolume("PCM", 0, volume); |
623 | | SetCurrentVolume("PCM", 1, volume); |
| 693 | case -EINTR: |
| 694 | isgood = true; // nuthin to see here |
| 695 | break; |
| 696 | case -ESTRPIPE: |
| 697 | suspense = true; |
| 698 | case -EPIPE: |
| 699 | if (!AlsaBad(snd_pcm_prepare(pcm.handle), |
| 700 | QString("failed to recover from %1") |
| 701 | .arg((suspense) ? "suspend" : "underrun"))) |
| 702 | isgood = true; |
| 703 | break; |
| 704 | default: |
| 705 | break; |
636 | | int err; |
637 | | |
638 | | QString alsadevice = gContext->GetSetting("MixerDevice", "default"); |
639 | | QString device = alsadevice.remove(QString("ALSA:")); |
640 | | |
641 | | if (mixer_handle != NULL) |
642 | | CloseMixer(); |
643 | | |
644 | | VERBOSE(VB_AUDIO, QString("Opening mixer %1").arg(device)); |
645 | | |
646 | | // TODO: This is opening card 0. Fix for case of multiple soundcards |
647 | | if ((err = snd_mixer_open(&mixer_handle, 0)) < 0) |
648 | | { |
649 | | Warn(QString("Mixer device open error %1: %2") |
650 | | .arg(err).arg(snd_strerror(err))); |
651 | | mixer_handle = NULL; |
652 | | return; |
653 | | } |
654 | | |
655 | | QByteArray dev = device.toAscii(); |
656 | | if ((err = snd_mixer_attach(mixer_handle, dev.constData())) < 0) |
657 | | { |
658 | | Warn(QString("Mixer attach error %1: %2" |
659 | | "\n\t\t\tCheck Mixer Name in Setup: '%3'") |
660 | | .arg(err).arg(snd_strerror(err)).arg(device)); |
661 | | CloseMixer(); |
662 | | return; |
663 | | } |
664 | | |
665 | | if ((err = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0) |
| 712 | bool isgood = false; |
| 713 | if (pcm.handle != NULL) |
667 | | Warn(QString("Mixer register error %1: %2") |
668 | | .arg(err).arg(snd_strerror(err))); |
669 | | CloseMixer(); |
670 | | return; |
671 | | } |
672 | | |
673 | | if ((err = snd_mixer_load(mixer_handle)) < 0) |
674 | | { |
675 | | Warn(QString("Mixer load error %1: %2") |
676 | | .arg(err).arg(snd_strerror(err))); |
677 | | CloseMixer(); |
678 | | return; |
| 715 | if (!AlsaBad(snd_pcm_drop(pcm.handle), "pcm drop failed")) |
| 716 | if (!AlsaBad(snd_pcm_prepare(pcm.handle), "pcm prepare failed")) |
| 717 | isgood = true; |
| 718 | VERBOSE(VB_AUDIO, LOC + pcm.logtag |
| 719 | + "xrun recovery " + ((isgood) ? "good" : "not good")); |