| 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")); |