alsalayer.cpp 26.1 KB
Newer Older
1
/*
Adrien Béraud's avatar
Adrien Béraud committed
2
 *  Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
4
 *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
Tristan Matthews's avatar
Tristan Matthews committed
5
 *  Author: Андрей Лухнов <aol.nnov@gmail.com>
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
19
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
20 21 22 23 24 25 26 27 28 29 30
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
31 32 33
 */

#include "alsalayer.h"
34
#include "logger.h"
35
#include "manager.h"
36
#include "noncopyable.h"
37
#include "client/configurationmanager.h"
38
#include "audio/ringbufferpool.h"
39
#include "audio/ringbuffer.h"
40
#include "audio/resampler.h"
41

Adrien Béraud's avatar
Adrien Béraud committed
42 43 44
#include <thread>
#include <atomic>

Adrien Béraud's avatar
Adrien Béraud committed
45
namespace ring {
46

47
class AlsaThread {
48
    public:
49
        AlsaThread(AlsaLayer *alsa);
50
        ~AlsaThread();
51
        void initAudioLayer();
52 53
        void start();
        bool isRunning() const;
54 55

    private:
56 57
        void run();

58
        NON_COPYABLE(AlsaThread);
Adrien Béraud's avatar
Adrien Béraud committed
59
        std::thread thread_;
60
        AlsaLayer* alsa_;
Adrien Béraud's avatar
Adrien Béraud committed
61
        std::atomic<bool> running_;
62 63
};

64
AlsaThread::AlsaThread(AlsaLayer *alsa)
Adrien Béraud's avatar
Adrien Béraud committed
65
    : thread_(), alsa_(alsa), running_(false)
66
{}
67

68 69 70 71 72 73 74 75
bool AlsaThread::isRunning() const
{
    return running_;
}

AlsaThread::~AlsaThread()
{
    running_ = false;
Tristan Matthews's avatar
Tristan Matthews committed
76
    if (thread_.joinable())
Adrien Béraud's avatar
Adrien Béraud committed
77
        thread_.join();
78 79 80 81 82
}

void AlsaThread::start()
{
    running_ = true;
Adrien Béraud's avatar
Adrien Béraud committed
83
    thread_ = std::thread(&AlsaThread::run, this);
84 85
}

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
void AlsaThread::initAudioLayer(void)
{
    std::string pcmp;
    std::string pcmr;
    std::string pcmc;

    if (alsa_->audioPlugin_ == PCM_DMIX_DSNOOP) {
        pcmp = alsa_->buildDeviceTopo(PCM_DMIX, alsa_->indexOut_);
        pcmr = alsa_->buildDeviceTopo(PCM_DMIX, alsa_->indexRing_);
        pcmc = alsa_->buildDeviceTopo(PCM_DSNOOP, alsa_->indexIn_);
    } else {
        pcmp = alsa_->buildDeviceTopo(alsa_->audioPlugin_, alsa_->indexOut_);
        pcmr = alsa_->buildDeviceTopo(alsa_->audioPlugin_, alsa_->indexRing_);
        pcmc = alsa_->buildDeviceTopo(alsa_->audioPlugin_, alsa_->indexIn_);
    }

    if (not alsa_->is_capture_open_) {
        alsa_->is_capture_open_ = alsa_->openDevice(&alsa_->captureHandle_, pcmc, SND_PCM_STREAM_CAPTURE);

        if (not alsa_->is_capture_open_)
106
            Manager::instance().getConfigurationManager()->errorAlert(ALSA_CAPTURE_DEVICE);
107 108 109 110 111 112
    }

    if (not alsa_->is_playback_open_) {
        alsa_->is_playback_open_ = alsa_->openDevice(&alsa_->playbackHandle_, pcmp, SND_PCM_STREAM_PLAYBACK);

        if (not alsa_->is_playback_open_)
113
            Manager::instance().getConfigurationManager()->errorAlert(ALSA_PLAYBACK_DEVICE);
114 115 116

        if (alsa_->getIndexPlayback() != alsa_->getIndexRingtone())
            if (!alsa_->openDevice(&alsa_->ringtoneHandle_, pcmr, SND_PCM_STREAM_PLAYBACK))
117
                Manager::instance().getConfigurationManager()->errorAlert(ALSA_PLAYBACK_DEVICE);
118 119
    }

120 121
    alsa_->hardwareFormatAvailable(alsa_->getFormat());

122 123 124 125 126 127 128 129 130 131
    alsa_->prepareCaptureStream();
    alsa_->preparePlaybackStream();

    alsa_->startCaptureStream();
    alsa_->startPlaybackStream();

    alsa_->flushMain();
    alsa_->flushUrgent();
}

132 133 134
/**
 * Reimplementation of run()
 */
135
void AlsaThread::run()
136
{
137
    initAudioLayer();
138
    alsa_->isStarted_ = true;
139

140
    while (alsa_->isStarted_ and running_) {
141
        alsa_->audioCallback();
142 143 144
    }
}

145
AlsaLayer::AlsaLayer(const AudioPreference &pref)
146 147
    : AudioLayer(pref)
    , indexIn_(pref.getAlsaCardin())
Tristan Matthews's avatar
Tristan Matthews committed
148 149
    , indexOut_(pref.getAlsaCardout())
    , indexRing_(pref.getAlsaCardring())
Adrien Béraud's avatar
Adrien Béraud committed
150 151 152
    , playbackHandle_(nullptr)
    , ringtoneHandle_(nullptr)
    , captureHandle_(nullptr)
Tristan Matthews's avatar
Tristan Matthews committed
153
    , audioPlugin_(pref.getAlsaPlugin())
154 155 156 157
    , playbackBuff_(0, audioFormat_)
    , captureBuff_(0, audioFormat_)
    , playbackIBuff_(1024)
    , captureIBuff_(1024)
158 159 160 161 162 163
    , is_playback_prepared_(false)
    , is_capture_prepared_(false)
    , is_playback_running_(false)
    , is_capture_running_(false)
    , is_playback_open_(false)
    , is_capture_open_(false)
Adrien Béraud's avatar
Adrien Béraud committed
164
    , audioThread_(nullptr)
165
    , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID))
166
{}
167

168
AlsaLayer::~AlsaLayer()
169
{
170
    isStarted_ = false;
171
    delete audioThread_;
172

Emmanuel Milou's avatar
Emmanuel Milou committed
173
    /* Then close the audio devices */
174 175
    closeCaptureStream();
    closePlaybackStream();
176
}
177

178
// Retry approach taken from pa_linux_alsa.c, part of PortAudio
179
bool AlsaLayer::openDevice(snd_pcm_t **pcm, const std::string &dev, snd_pcm_stream_t stream)
180
{
Adrien Béraud's avatar
Adrien Béraud committed
181
    RING_DBG("Alsa: Opening %s",  dev.c_str());
182

183 184
    static const int MAX_RETRIES = 100;
    int err = snd_pcm_open(pcm, dev.c_str(), stream, 0);
185

186 187
    // Retry if busy, since dmix plugin may not have released the device yet
    for (int tries = 0; tries < MAX_RETRIES and err == -EBUSY; ++tries) {
188
        const struct timespec req = {0, 100000000L};
189
        nanosleep(&req, 0);
190 191 192
        err = snd_pcm_open(pcm, dev.c_str(), stream, 0);
    }

193
    if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
194
        RING_ERR("Alsa: couldn't open device %s : %s",  dev.c_str(),
195
              snd_strerror(err));
196 197 198
        return false;
    }

199 200 201 202
    if (!alsa_set_params(*pcm)) {
        snd_pcm_close(*pcm);
        return false;
    }
203

204
    return true;
205 206
}

207
void
208
AlsaLayer::startStream()
209
{
210
    dcblocker_.reset();
211

Tristan Matthews's avatar
Tristan Matthews committed
212
    if (is_playback_running_ and is_capture_running_)
213 214
        return;

Tristan Matthews's avatar
Tristan Matthews committed
215
    if (audioThread_ == NULL) {
216 217
        audioThread_ = new AlsaThread(this);
        audioThread_->start();
218 219
    } else if (!audioThread_->isRunning()) {
        audioThread_->start();
220
    }
221
}
222

223
void
224
AlsaLayer::stopStream()
225
{
226
    isStarted_ = false;
227

228 229
    delete audioThread_;
    audioThread_ = NULL;
230

Rafaël Carré's avatar
Rafaël Carré committed
231 232
    closeCaptureStream();
    closePlaybackStream();
233

Tristan Matthews's avatar
Tristan Matthews committed
234 235 236
    playbackHandle_ = NULL;
    captureHandle_ = NULL;
    ringtoneHandle_ = NULL;
237

Emmanuel Milou's avatar
Emmanuel Milou committed
238
    /* Flush the ring buffers */
Rafaël Carré's avatar
Rafaël Carré committed
239 240
    flushUrgent();
    flushMain();
241 242
}

243 244 245 246
/*
 * GCC extension : statement expression
 *
 * ALSA_CALL(function_call, error_string) will:
247 248 249
 *  call the function
 *  display an error if the function failed
 *  return the function return value
250 251
 */
#define ALSA_CALL(call, error) ({ \
252 253
        int err_code = call; \
        if (err_code < 0) \
Adrien Béraud's avatar
Adrien Béraud committed
254
            RING_ERR(error ": %s", snd_strerror(err_code)); \
255 256
            err_code; \
        })
257

258
void AlsaLayer::stopCaptureStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
259
{
260 261 262
    if (captureHandle_ && ALSA_CALL(snd_pcm_drop(captureHandle_), "couldn't stop capture") >= 0) {
        is_capture_running_ = false;
        is_capture_prepared_ = false;
Emmanuel Milou's avatar
Emmanuel Milou committed
263 264
    }
}
265

266
void AlsaLayer::closeCaptureStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
267
{
Tristan Matthews's avatar
Tristan Matthews committed
268
    if (is_capture_prepared_ and is_capture_running_)
269
        stopCaptureStream();
270

271 272
    if (is_capture_open_ && ALSA_CALL(snd_pcm_close(captureHandle_), "Couldn't close capture") >= 0)
        is_capture_open_ = false;
273 274
}

275
void AlsaLayer::startCaptureStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
276
{
277
    if (captureHandle_ and not is_capture_running_)
278
        if (ALSA_CALL(snd_pcm_start(captureHandle_), "Couldn't start capture") >= 0)
Tristan Matthews's avatar
Tristan Matthews committed
279
            is_capture_running_ = true;
Emmanuel Milou's avatar
Emmanuel Milou committed
280 281
}

282
void AlsaLayer::stopPlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
283
{
284
    if (ringtoneHandle_ and is_playback_running_)
285
        ALSA_CALL(snd_pcm_drop(ringtoneHandle_), "Couldn't stop ringtone");
286

Tristan Matthews's avatar
Tristan Matthews committed
287
    if (playbackHandle_ and is_playback_running_) {
288
        if (ALSA_CALL(snd_pcm_drop(playbackHandle_), "Couldn't stop playback") >= 0) {
Tristan Matthews's avatar
Tristan Matthews committed
289 290 291
            is_playback_running_ = false;
            is_playback_prepared_ = false;
        }
Emmanuel Milou's avatar
Emmanuel Milou committed
292 293 294 295
    }
}


296
void AlsaLayer::closePlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
297
{
Tristan Matthews's avatar
Tristan Matthews committed
298 299
    if (is_playback_prepared_ and is_playback_running_)
        stopPlaybackStream();
300

Tristan Matthews's avatar
Tristan Matthews committed
301
    if (is_playback_open_) {
302
        if (ringtoneHandle_)
303
            ALSA_CALL(snd_pcm_close(ringtoneHandle_), "Couldn't stop ringtone");
Emmanuel Milou's avatar
Emmanuel Milou committed
304

305
        if (ALSA_CALL(snd_pcm_close(playbackHandle_), "Coulnd't close playback") >= 0)
Tristan Matthews's avatar
Tristan Matthews committed
306
            is_playback_open_ = false;
307
    }
308

Emmanuel Milou's avatar
Emmanuel Milou committed
309 310
}

311
void AlsaLayer::startPlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
312
{
313
    is_playback_running_ = true;
Emmanuel Milou's avatar
Emmanuel Milou committed
314 315
}

316
void AlsaLayer::prepareCaptureStream()
Rafaël Carré's avatar
Rafaël Carré committed
317
{
318
    if (is_capture_open_ and not is_capture_prepared_)
319
        if (ALSA_CALL(snd_pcm_prepare(captureHandle_), "Couldn't prepare capture") >= 0)
Rafaël Carré's avatar
Rafaël Carré committed
320 321 322
            is_capture_prepared_ = true;
}

323
void AlsaLayer::preparePlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
324
{
325
    is_playback_prepared_ = true;
326 327
}

328
bool AlsaLayer::alsa_set_params(snd_pcm_t *pcm_handle)
329
{
330
#define TRY(call, error) do { \
331 332 333
    if (ALSA_CALL(call, error) < 0) \
    return false; \
} while(0)
334 335 336 337

    snd_pcm_hw_params_t *hwparams;
    snd_pcm_hw_params_alloca(&hwparams);

Adrien Béraud's avatar
Adrien Béraud committed
338 339 340
    const unsigned RING_ALSA_PERIOD_SIZE = 160;
    const unsigned RING_ALSA_NB_PERIOD = 8;
    const unsigned RING_ALSA_BUFFER_SIZE = RING_ALSA_PERIOD_SIZE * RING_ALSA_NB_PERIOD;
341

Adrien Béraud's avatar
Adrien Béraud committed
342 343 344
    snd_pcm_uframes_t period_size = RING_ALSA_PERIOD_SIZE;
    snd_pcm_uframes_t buffer_size = RING_ALSA_BUFFER_SIZE;
    unsigned int periods = RING_ALSA_NB_PERIOD;
345 346 347 348 349

    snd_pcm_uframes_t  period_size_min = 0;
    snd_pcm_uframes_t  period_size_max = 0;
    snd_pcm_uframes_t  buffer_size_min = 0;
    snd_pcm_uframes_t  buffer_size_max = 0;
350 351

#define HW pcm_handle, hwparams /* hardware parameters */
352
    TRY(snd_pcm_hw_params_any(HW), "hwparams init");
353

354 355
    TRY(snd_pcm_hw_params_set_access(HW, SND_PCM_ACCESS_RW_INTERLEAVED), "access type");
    TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_S16_LE), "sample format");
356 357 358 359 360 361 362

    TRY(snd_pcm_hw_params_set_rate_resample(HW, 0), "hardware sample rate"); /* prevent software resampling */
    TRY(snd_pcm_hw_params_set_rate_near(HW, &audioFormat_.sample_rate, nullptr), "sample rate");

    // TODO: use snd_pcm_query_chmaps or similar to get hardware channel num
    audioFormat_.nb_channels = 2;
    TRY(snd_pcm_hw_params_set_channels_near(HW, &audioFormat_.nb_channels), "channel count");
363 364 365

    snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
    snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
366 367
    snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr);
    snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr);
Adrien Béraud's avatar
Adrien Béraud committed
368 369
    RING_DBG("Buffer size range from %lu to %lu", buffer_size_min, buffer_size_max);
    RING_DBG("Period size range from %lu to %lu", period_size_min, period_size_max);
370 371 372 373 374 375
    buffer_size = buffer_size > buffer_size_max ? buffer_size_max : buffer_size;
    buffer_size = buffer_size < buffer_size_min ? buffer_size_min : buffer_size;
    period_size = period_size > period_size_max ? period_size_max : period_size;
    period_size = period_size < period_size_min ? period_size_min : period_size;

    TRY(snd_pcm_hw_params_set_buffer_size_near(HW, &buffer_size), "Unable to set buffer size for playback");
376 377
    TRY(snd_pcm_hw_params_set_period_size_near(HW, &period_size, nullptr), "Unable to set period size for playback");
    TRY(snd_pcm_hw_params_set_periods_near(HW, &periods, nullptr), "Unable to set number of periods for playback");
378
    TRY(snd_pcm_hw_params(HW), "hwparams");
379 380

    snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
381 382 383
    snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr);
    snd_pcm_hw_params_get_rate(hwparams, &audioFormat_.sample_rate, nullptr);
    snd_pcm_hw_params_get_channels(hwparams, &audioFormat_.nb_channels);
Adrien Béraud's avatar
Adrien Béraud committed
384 385
    RING_DBG("Was set period_size = %lu", period_size);
    RING_DBG("Was set buffer_size = %lu", buffer_size);
386

Tristan Matthews's avatar
Tristan Matthews committed
387
    if (2 * period_size > buffer_size) {
Adrien Béraud's avatar
Adrien Béraud committed
388
        RING_ERR("buffer to small, could not use");
389 390
        return false;
    }
391

392 393
#undef HW

Adrien Béraud's avatar
Adrien Béraud committed
394
    RING_DBG("%s using format %s",
395
          (snd_pcm_stream(pcm_handle) == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
396
          audioFormat_.toString().c_str() );
397

398 399
    snd_pcm_sw_params_t *swparams = NULL;
    snd_pcm_sw_params_alloca(&swparams);
400

401
#define SW pcm_handle, swparams /* software parameters */
402
    snd_pcm_sw_params_current(SW);
403
    TRY(snd_pcm_sw_params_set_start_threshold(SW, period_size * 2), "start threshold");
404
    TRY(snd_pcm_sw_params(SW), "sw parameters");
405
#undef SW
406

407
    return true;
408 409

#undef TRY
410 411
}

412 413
// TODO first frame causes broken pipe (underrun) because not enough data is sent
// we should wait until the handle is ready
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
414
void
Guillaume Roguez's avatar
Guillaume Roguez committed
415
AlsaLayer::write(AudioSample* buffer, int frames, snd_pcm_t * handle)
416
{
417
    // Skip empty buffers
418
    if (!frames)
419
        return;
420

421
    int err = snd_pcm_writei(handle, (const void*)buffer, frames);
422

423 424 425
    if (err < 0)
        snd_pcm_recover(handle, err, 0);

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
426
    if (err >= 0)
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
        return;

    switch (err) {

        case -EPIPE:
        case -ESTRPIPE:
        case -EIO: {
            snd_pcm_status_t* status;
            snd_pcm_status_alloca(&status);

            if (ALSA_CALL(snd_pcm_status(handle, status), "Cannot get playback handle status") >= 0)
                if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
                    stopPlaybackStream();
                    preparePlaybackStream();
                    startPlaybackStream();
                }

444
            ALSA_CALL(snd_pcm_writei(handle, (const void*)buffer, frames), "XRUN handling failed");
445 446 447
            break;
        }

448 449 450 451 452 453
        case -EBADFD: {
            snd_pcm_status_t* status;
            snd_pcm_status_alloca(&status);

            if (ALSA_CALL(snd_pcm_status(handle, status), "Cannot get playback handle status") >= 0) {
                if (snd_pcm_status_get_state(status) == SND_PCM_STATE_SETUP) {
Adrien Béraud's avatar
Adrien Béraud committed
454
                    RING_ERR("Writing in state SND_PCM_STATE_SETUP, should be "
455 456
                          "SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING");
                    int error = snd_pcm_prepare(handle);
457

458
                    if (error < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
459
                        RING_ERR("Failed to prepare handle: %s", snd_strerror(error));
460 461 462 463
                        stopPlaybackStream();
                    }
                }
            }
464

465 466 467
            break;
        }

468
        default:
Adrien Béraud's avatar
Adrien Béraud committed
469
            RING_ERR("Unknown write error, dropping frames: %s", snd_strerror(err));
470 471 472
            stopPlaybackStream();
            break;
    }
473 474
}

475
int
Guillaume Roguez's avatar
Guillaume Roguez committed
476
AlsaLayer::read(AudioSample* buffer, int frames)
477
{
478 479 480
    if (snd_pcm_state(captureHandle_) == SND_PCM_STATE_XRUN) {
        prepareCaptureStream();
        startCaptureStream();
481
    }
482

483
    int err = snd_pcm_readi(captureHandle_, (void*)buffer, frames);
484

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
485
    if (err >= 0)
486
        return err;
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501

    switch (err) {
        case -EPIPE:
        case -ESTRPIPE:
        case -EIO: {
            snd_pcm_status_t* status;
            snd_pcm_status_alloca(&status);

            if (ALSA_CALL(snd_pcm_status(captureHandle_, status), "Get status failed") >= 0)
                if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
                    stopCaptureStream();
                    prepareCaptureStream();
                    startCaptureStream();
                }

Adrien Béraud's avatar
Adrien Béraud committed
502
            RING_ERR("XRUN capture ignored (%s)", snd_strerror(err));
503 504 505
            break;
        }

506
        case -EPERM:
Adrien Béraud's avatar
Adrien Béraud committed
507
            RING_ERR("Can't capture, EPERM (%s)", snd_strerror(err));
508 509 510 511 512 513
            prepareCaptureStream();
            startCaptureStream();
            break;
    }

    return 0;
514 515
}

516
std::string
517
AlsaLayer::buildDeviceTopo(const std::string &plugin, int card)
518
{
519
    std::stringstream ss;
Tristan Matthews's avatar
Tristan Matthews committed
520
    std::string pcm(plugin);
521

522
    if (pcm == PCM_DEFAULT)
523
        return pcm;
524

525
    ss << ":" << card;
526

527
    return pcm + ss.str();
528 529
}

530 531
static bool
safeUpdate(snd_pcm_t *handle, int &samples)
532 533
{
    samples = snd_pcm_avail_update(handle);
534

535 536
    if (samples < 0) {
        samples = snd_pcm_recover(handle, samples, 0);
537

538
        if (samples < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
539
            RING_ERR("Got unrecoverable error from snd_pcm_avail_update: %s", snd_strerror(samples));
540 541 542
            return false;
        }
    }
543

544 545 546
    return true;
}

547
static std::vector<std::string>
548
getValues(const std::vector<HwIDPair> &deviceMap)
549
{
550
    std::vector<std::string> audioDeviceList;
551

Tristan Matthews's avatar
Tristan Matthews committed
552
    for (const auto & dev : deviceMap)
553
        audioDeviceList.push_back(dev.second);
554

555 556 557
    return audioDeviceList;
}

558 559 560 561 562 563 564 565 566 567 568
std::vector<std::string>
AlsaLayer::getCaptureDeviceList() const
{
    return getValues(getAudioDeviceIndexMap(true));
}

std::vector<std::string>
AlsaLayer::getPlaybackDeviceList() const
{
    return getValues(getAudioDeviceIndexMap(false));
}
569 570

std::vector<HwIDPair>
571
AlsaLayer::getAudioDeviceIndexMap(bool getCapture) const
572
{
573 574 575
    snd_ctl_t* handle;
    snd_ctl_card_info_t *info;
    snd_pcm_info_t* pcminfo;
576 577
    snd_ctl_card_info_alloca(&info);
    snd_pcm_info_alloca(&pcminfo);
578

579
    int numCard = -1;
580

581
    std::vector<HwIDPair> audioDevice;
582 583

    if (snd_card_next(&numCard) < 0 || numCard < 0)
584
        return audioDevice;
585

586
    do {
587 588
        std::stringstream ss;
        ss << numCard;
589
        std::string name = "hw:" + ss.str();
590

591 592
        if (snd_ctl_open(&handle, name.c_str(), 0) == 0) {
            if (snd_ctl_card_info(handle, info) == 0) {
593
                snd_pcm_info_set_device(pcminfo, 0);
594
                snd_pcm_info_set_stream(pcminfo, getCapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
595

596
                int err;
Tristan Matthews's avatar
Tristan Matthews committed
597

598
                if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
599
                    RING_WARN("Cannot get info for %s %s: %s", getCapture ?
600 601
                         "capture device" : "playback device", name.c_str(),
                         snd_strerror(err));
602
                } else {
Adrien Béraud's avatar
Adrien Béraud committed
603
                    RING_DBG("card %i : %s [%s]",
604 605 606
                          numCard,
                          snd_ctl_card_info_get_id(info),
                          snd_ctl_card_info_get_name(info));
607 608 609
                    std::string description = snd_ctl_card_info_get_name(info);
                    description.append(" - ");
                    description.append(snd_pcm_info_get_name(pcminfo));
610

611
                    // The number of the sound card is associated with a string description
612
                    audioDevice.push_back(HwIDPair(numCard, description));
613 614
                }
            }
615

616
            snd_ctl_close(handle);
617
        }
618
    } while (snd_card_next(&numCard) >= 0 && numCard >= 0);
619 620


621
    return audioDevice;
622 623 624
}


625
bool
626
AlsaLayer::soundCardIndexExists(int card, DeviceType stream)
627
{
628
    snd_pcm_info_t *pcminfo;
629 630
    snd_pcm_info_alloca(&pcminfo);
    std::string name("hw:");
631
    std::stringstream ss;
632 633
    ss << card;
    name.append(ss.str());
634

635
    snd_ctl_t* handle;
636

637
    if (snd_ctl_open(&handle, name.c_str(), 0) != 0)
638
        return false;
639

640 641
    snd_pcm_info_set_stream(pcminfo, stream == DeviceType::PLAYBACK ?  SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE);
    bool ret = snd_ctl_pcm_info(handle, pcminfo) >= 0;
642 643
    snd_ctl_close(handle);
    return ret;
644
}
645

646
int
647
AlsaLayer::getAudioDeviceIndex(const std::string &description, DeviceType type) const
648
{
649
    std::vector<HwIDPair> devices = getAudioDeviceIndexMap(type == DeviceType::CAPTURE);
650

651
    for (const auto & dev : devices)
652 653
        if (dev.second == description)
            return dev.first;
654

655 656
    // else return the default one
    return 0;
657 658
}

659
std::string
660
AlsaLayer::getAudioDeviceName(int index, DeviceType type) const
661 662 663 664 665
{
    // a bit ugly and wrong.. i do not know how to implement it better in alsalayer.
    // in addition, for now it is used in pulselayer only due to alsa and pulse layers api differences.
    // but after some tweaking in alsalayer, it could be used in it too.
    switch (type) {
666 667
        case DeviceType::PLAYBACK:
        case DeviceType::RINGTONE:
668
            return getPlaybackDeviceList().at(index);
669

670
        case DeviceType::CAPTURE:
671
            return getCaptureDeviceList().at(index);
672 673
        default:
            // Should never happen
Adrien Béraud's avatar
Adrien Béraud committed
674
            RING_ERR("Unexpected type");
675
            return "";
676 677 678
    }
}

679
void AlsaLayer::capture()
680
{
681
    AudioFormat mainBufferFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat();
682

683
    int toGetFrames = snd_pcm_avail_update(captureHandle_);
684

685
    if (toGetFrames < 0)
Adrien Béraud's avatar
Adrien Béraud committed
686
        RING_ERR("Audio: Mic error: %s", snd_strerror(toGetFrames));
687

688
    if (toGetFrames <= 0)
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
689 690 691
        return;

    const int framesPerBufferAlsa = 2048;
692 693
    toGetFrames = std::min(framesPerBufferAlsa, toGetFrames);
    captureIBuff_.resize(toGetFrames * audioFormat_.nb_channels);
694

695
    if (read(captureIBuff_.data(), toGetFrames) != toGetFrames) {
Adrien Béraud's avatar
Adrien Béraud committed
696
        RING_ERR("ALSA MIC : Couldn't read!");
Tristan Matthews's avatar
Tristan Matthews committed
697
        return;
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
698
    }
699

700 701
    captureBuff_.deinterleave(captureIBuff_, audioFormat_);
    captureBuff_.applyGain(isCaptureMuted_ ? 0.0 : captureGain_);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
702

703 704 705 706 707 708
    if (audioFormat_.nb_channels != mainBufferFormat.nb_channels) {
        captureBuff_.setChannelNum(mainBufferFormat.nb_channels, true);
    }
    if (audioFormat_.sample_rate != mainBufferFormat.sample_rate) {
        int outFrames = toGetFrames * (static_cast<double>(audioFormat_.sample_rate) / mainBufferFormat.sample_rate);
        AudioBuffer rsmpl_in(outFrames, mainBufferFormat);
709
        resampler_->resample(captureBuff_, rsmpl_in);
710
        dcblocker_.process(rsmpl_in);
711
        mainRingBuffer_->put(rsmpl_in);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
712
    } else {
713
        dcblocker_.process(captureBuff_);
714
        mainRingBuffer_->put(captureBuff_);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
715 716
    }
}
717

718
void AlsaLayer::playback(int maxFrames)
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
719
{
720
    unsigned framesToGet = Manager::instance().getRingBufferPool().availableForGet(RingBufferPool::DEFAULT_ID);
721

722
    // no audio available, play tone or silence
723
    if (framesToGet <= 0) {
724 725
        // FIXME: not thread safe! we only lock the mutex when we get the
        // pointer, we have no guarantee that it will stay safe to use
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
726 727
        AudioLoop *tone = Manager::instance().getTelephoneTone();
        AudioLoop *file_tone = Manager::instance().getTelephoneFile();
728

729 730
        playbackBuff_.setFormat(audioFormat_);
        playbackBuff_.resize(maxFrames);
731

732
        if (tone)