alsalayer.cpp 25.8 KB
Newer Older
1
/*
2
 *  Copyright (C) 2004-2013 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 35 36
#include "audio/dcblocker.h"
#include "eventthread.h"
#include "audio/samplerateconverter.h"
37
#include "logger.h"
38
#include "manager.h"
39
#include "noncopyable.h"
40
#include "client/configurationmanager.h"
41
#include <ctime>
42

43 44
#define SFL_ALSA_PERIOD_SIZE 160
#define SFL_ALSA_NB_PERIOD 8
45
#define SFL_ALSA_BUFFER_SIZE SFL_ALSA_PERIOD_SIZE * SFL_ALSA_NB_PERIOD
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 58
        void run();
        static void *runCallback(void *context);

59
        NON_COPYABLE(AlsaThread);
60
        pthread_t thread_;
61
        AlsaLayer* alsa_;
62
        bool running_;
63 64
};

65
AlsaThread::AlsaThread(AlsaLayer *alsa)
66
    : thread_(0), alsa_(alsa), running_(false)
67
{}
68

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

AlsaThread::~AlsaThread()
{
    running_ = false;
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
    if (thread_)
        pthread_join(thread_, NULL);
}

void AlsaThread::start()
{
    running_ = true;
    pthread_create(&thread_, NULL, &runCallback, this);
}

void *
AlsaThread::runCallback(void *data)
{
    AlsaThread *context = static_cast<AlsaThread*>(data);
    context->run();
    return NULL;
}

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
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_)
116
            Manager::instance().getClient()->getConfigurationManager()->errorAlert(ALSA_CAPTURE_DEVICE);
117 118 119 120 121 122
    }

    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_)
123
            Manager::instance().getClient()->getConfigurationManager()->errorAlert(ALSA_PLAYBACK_DEVICE);
124 125 126

        if (alsa_->getIndexPlayback() != alsa_->getIndexRingtone())
            if (!alsa_->openDevice(&alsa_->ringtoneHandle_, pcmr, SND_PCM_STREAM_PLAYBACK))
127
                Manager::instance().getClient()->getConfigurationManager()->errorAlert(ALSA_PLAYBACK_DEVICE);
128 129 130 131 132 133 134 135 136 137 138 139
    }

    alsa_->prepareCaptureStream();
    alsa_->preparePlaybackStream();

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

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

140 141 142
/**
 * Reimplementation of run()
 */
143
void AlsaThread::run()
144
{
145 146
    initAudioLayer();

147
    while (alsa_->isStarted_) {
148
        alsa_->audioCallback();
149
        usleep(20000); // 20 ms
150 151 152
    }
}

153
AlsaLayer::AlsaLayer(const AudioPreference &pref)
Tristan Matthews's avatar
Tristan Matthews committed
154 155 156
    : indexIn_(pref.getAlsaCardin())
    , indexOut_(pref.getAlsaCardout())
    , indexRing_(pref.getAlsaCardring())
157 158
    , watchdogTotalCount_(0)
    , watchdogTotalErr_(0)
159 160 161
    , playbackHandle_(NULL)
    , ringtoneHandle_(NULL)
    , captureHandle_(NULL)
Tristan Matthews's avatar
Tristan Matthews committed
162
    , audioPlugin_(pref.getAlsaPlugin())
163 164 165 166 167 168 169
    , is_playback_prepared_(false)
    , is_capture_prepared_(false)
    , is_playback_running_(false)
    , is_capture_running_(false)
    , is_playback_open_(false)
    , is_capture_open_(false)
    , audioThread_(NULL)
170
{
171 172
    setCaptureGain(pref.getVolumemic());
    setPlaybackGain(pref.getVolumespkr());
173 174
}

175
AlsaLayer::~AlsaLayer()
176
{
177
    isStarted_ = false;
178
    delete audioThread_;
179

Emmanuel Milou's avatar
Emmanuel Milou committed
180
    /* Then close the audio devices */
181 182
    closeCaptureStream();
    closePlaybackStream();
183
}
184

185
// Retry approach taken from pa_linux_alsa.c, part of PortAudio
186
bool AlsaLayer::openDevice(snd_pcm_t **pcm, const std::string &dev, snd_pcm_stream_t stream)
187
{
188 189
    static const int MAX_RETRIES = 100;
    int err = snd_pcm_open(pcm, dev.c_str(), stream, 0);
190

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

198
    if (err < 0) {
199
        ERROR("Alsa: couldn't open device %s : %s",  dev.c_str(),
200
              snd_strerror(err));
201 202 203
        return false;
    }

204 205 206 207
    if (!alsa_set_params(*pcm)) {
        snd_pcm_close(*pcm);
        return false;
    }
208

209
    return true;
210 211
}

212
void
213
AlsaLayer::startStream()
214
{
215
    dcblocker_.reset();
216

Tristan Matthews's avatar
Tristan Matthews committed
217
    if (is_playback_running_ and is_capture_running_)
218 219
        return;

Tristan Matthews's avatar
Tristan Matthews committed
220
    if (audioThread_ == NULL) {
221 222
        audioThread_ = new AlsaThread(this);
        audioThread_->start();
223 224
    } else if (!audioThread_->isRunning()) {
        audioThread_->start();
225
    }
226

227
    isStarted_ = true;
228
}
229

230
void
231
AlsaLayer::stopStream()
232
{
233
    isStarted_ = false;
234

235 236
    delete audioThread_;
    audioThread_ = NULL;
237

Rafaël Carré's avatar
Rafaël Carré committed
238 239
    closeCaptureStream();
    closePlaybackStream();
240

Tristan Matthews's avatar
Tristan Matthews committed
241 242 243
    playbackHandle_ = NULL;
    captureHandle_ = NULL;
    ringtoneHandle_ = NULL;
244

Emmanuel Milou's avatar
Emmanuel Milou committed
245
    /* Flush the ring buffers */
Rafaël Carré's avatar
Rafaël Carré committed
246 247
    flushUrgent();
    flushMain();
248 249
}

250 251 252 253 254 255 256 257 258
/*
 * GCC extension : statement expression
 *
 * ALSA_CALL(function_call, error_string) will:
 * 		call the function
 * 		display an error if the function failed
 * 		return the function return value
 */
#define ALSA_CALL(call, error) ({ \
259 260
			int err_code = call; \
			if (err_code < 0) \
261
				ERROR(error ": %s", snd_strerror(err_code)); \
262
			err_code; \
263 264
		})

265
void AlsaLayer::stopCaptureStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
266
{
267 268 269
    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
270 271
    }
}
272

273
void AlsaLayer::closeCaptureStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
274
{
Tristan Matthews's avatar
Tristan Matthews committed
275
    if (is_capture_prepared_ and is_capture_running_)
276
        stopCaptureStream();
277

278 279
    if (is_capture_open_ && ALSA_CALL(snd_pcm_close(captureHandle_), "Couldn't close capture") >= 0)
        is_capture_open_ = false;
280 281
}

282
void AlsaLayer::startCaptureStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
283
{
284
    if (captureHandle_ and not is_capture_running_)
285
        if (ALSA_CALL(snd_pcm_start(captureHandle_), "Couldn't start capture") >= 0)
Tristan Matthews's avatar
Tristan Matthews committed
286
            is_capture_running_ = true;
Emmanuel Milou's avatar
Emmanuel Milou committed
287 288
}

289
void AlsaLayer::stopPlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
290
{
291
    if (ringtoneHandle_ and is_playback_running_)
292
        ALSA_CALL(snd_pcm_drop(ringtoneHandle_), "Couldn't stop ringtone");
293

Tristan Matthews's avatar
Tristan Matthews committed
294
    if (playbackHandle_ and is_playback_running_) {
295
        if (ALSA_CALL(snd_pcm_drop(playbackHandle_), "Couldn't stop playback") >= 0) {
Tristan Matthews's avatar
Tristan Matthews committed
296 297 298
            is_playback_running_ = false;
            is_playback_prepared_ = false;
        }
Emmanuel Milou's avatar
Emmanuel Milou committed
299 300 301 302
    }
}


303
void AlsaLayer::closePlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
304
{
Tristan Matthews's avatar
Tristan Matthews committed
305 306
    if (is_playback_prepared_ and is_playback_running_)
        stopPlaybackStream();
307

Tristan Matthews's avatar
Tristan Matthews committed
308
    if (is_playback_open_) {
309
        if (ringtoneHandle_)
310
            ALSA_CALL(snd_pcm_close(ringtoneHandle_), "Couldn't stop ringtone");
Emmanuel Milou's avatar
Emmanuel Milou committed
311

312
        if (ALSA_CALL(snd_pcm_close(playbackHandle_), "Coulnd't close playback") >= 0)
Tristan Matthews's avatar
Tristan Matthews committed
313
            is_playback_open_ = false;
314
    }
315

Emmanuel Milou's avatar
Emmanuel Milou committed
316 317
}

318
void AlsaLayer::startPlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
319
{
320
    is_playback_running_ = true;
Emmanuel Milou's avatar
Emmanuel Milou committed
321 322
}

323
void AlsaLayer::prepareCaptureStream()
Rafaël Carré's avatar
Rafaël Carré committed
324
{
325
    if (is_capture_open_ and not is_capture_prepared_)
326
        if (ALSA_CALL(snd_pcm_prepare(captureHandle_), "Couldn't prepare capture") >= 0)
Rafaël Carré's avatar
Rafaël Carré committed
327 328 329
            is_capture_prepared_ = true;
}

330
void AlsaLayer::preparePlaybackStream()
Emmanuel Milou's avatar
Emmanuel Milou committed
331
{
332
    is_playback_prepared_ = true;
333 334
}

335
bool AlsaLayer::alsa_set_params(snd_pcm_t *pcm_handle)
336
{
337
#define TRY(call, error) do { \
338
		if (ALSA_CALL(call, error) < 0) \
339 340 341 342 343 344
			return false; \
	} while(0)

    snd_pcm_hw_params_t *hwparams;
    snd_pcm_hw_params_alloca(&hwparams);

345 346 347 348 349 350 351 352
    snd_pcm_uframes_t period_size = SFL_ALSA_PERIOD_SIZE;
    snd_pcm_uframes_t buffer_size = SFL_ALSA_BUFFER_SIZE;
    unsigned int periods = SFL_ALSA_NB_PERIOD;

    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;
353 354

#define HW pcm_handle, hwparams /* hardware parameters */
355 356 357
    TRY(snd_pcm_hw_params_any(HW), "hwparams init");
    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");
358
    TRY(snd_pcm_hw_params_set_rate_near(HW, &sampleRate_, NULL), "sample rate");
359
    TRY(snd_pcm_hw_params_set_channels(HW, 1), "channel count");
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

    snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
    snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
    snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, NULL);
    snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, NULL);
    DEBUG("Buffer size range from %lu to %lu", buffer_size_min, buffer_size_max);
    DEBUG("Period size range from %lu to %lu", period_size_min, period_size_max);
    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");
    TRY(snd_pcm_hw_params_set_period_size_near(HW, &period_size, NULL), "Unable to set period size for playback");
    TRY(snd_pcm_hw_params_set_periods_near(HW, &periods, NULL), "Unable to set number of periods for playback");
375
    TRY(snd_pcm_hw_params(HW), "hwparams");
376 377 378 379 380 381 382 383 384 385

    snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
    snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
    DEBUG("Was set period_size = %lu", period_size);
    DEBUG("Was set buffer_size = %lu", buffer_size);

    if (2*period_size > buffer_size) {
        ERROR("buffer to small, could not use");
        return false;
    }
386

387 388
#undef HW

389
    DEBUG("%s using sampling rate %dHz",
390 391
          (snd_pcm_stream(pcm_handle) == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
          sampleRate_);
392

393 394
    snd_pcm_sw_params_t *swparams = NULL;
    snd_pcm_sw_params_alloca(&swparams);
395

396
#define SW pcm_handle, swparams /* software parameters */
397
    snd_pcm_sw_params_current(SW);
398
    TRY(snd_pcm_sw_params_set_start_threshold(SW, period_size * 2), "start threshold");
399
    TRY(snd_pcm_sw_params(SW), "sw parameters");
400
#undef SW
401

402
    return true;
403 404

#undef TRY
405 406
}

407 408
// 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
409
void
410
AlsaLayer::write(void* buffer, int length, snd_pcm_t * handle)
411
{
412
    // Skip empty buffers
413
    if (!length)
414
        return;
415

416
    snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames(handle, length);
417
    watchdogTotalCount_++;
418 419

    int err = snd_pcm_writei(handle, buffer , frames);
420

421 422 423
    if (err < 0)
        snd_pcm_recover(handle, err, 0);

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
424
    if (err >= 0)
425 426
        return;

427 428
    watchdogTotalErr_++;

429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
    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();
                }

            ALSA_CALL(snd_pcm_writei(handle, buffer , frames), "XRUN handling failed");
            break;
        }

448 449 450 451 452 453 454 455 456
        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) {
                    ERROR("Writing in state SND_PCM_STATE_SETUP, should be "
                          "SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING");
                    int error = snd_pcm_prepare(handle);
457

458 459 460 461 462 463
                    if (error < 0) {
                        ERROR("Failed to prepare handle: %s", snd_strerror(error));
                        stopPlaybackStream();
                    }
                }
            }
464

465 466 467
            break;
        }

468
        default:
469
            ERROR("Unknown write error, dropping frames: %s", snd_strerror(err));
470 471 472
            stopPlaybackStream();
            break;
    }
473

474 475 476
    // Detect when something is going wrong. This can be caused by alsa bugs or
    // faulty encoder on the other side
    // TODO do something useful instead of just warning and flushing buffers
477 478 479 480 481
    if (watchdogTotalErr_ > 0 && watchdogTotalCount_ / watchdogTotalErr_ >=4 && watchdogTotalCount_ > 50) {
        ERROR("Alsa: too many errors (%d error on %d frame)",watchdogTotalErr_,watchdogTotalCount_);
        flushUrgent();
        flushMain();
    }
482 483
}

484
int
485
AlsaLayer::read(void* buffer, int toCopy)
486
{
487 488 489
    if (snd_pcm_state(captureHandle_) == SND_PCM_STATE_XRUN) {
        prepareCaptureStream();
        startCaptureStream();
490
    }
491

492 493 494
    snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames(captureHandle_, toCopy);

    int err = snd_pcm_readi(captureHandle_, buffer, frames);
495

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
496
    if (err >= 0)
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
        return snd_pcm_frames_to_bytes(captureHandle_, frames);

    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();
                }

513
            ERROR("XRUN capture ignored (%s)", snd_strerror(err));
514 515 516
            break;
        }

517
        case -EPERM:
518
            ERROR("Can't capture, EPERM (%s)", snd_strerror(err));
519 520 521 522 523 524
            prepareCaptureStream();
            startCaptureStream();
            break;
    }

    return 0;
525 526
}

527
std::string
528
AlsaLayer::buildDeviceTopo(const std::string &plugin, int card)
529
{
530
    std::stringstream ss;
Tristan Matthews's avatar
Tristan Matthews committed
531
    std::string pcm(plugin);
532

533
    if (pcm == PCM_DEFAULT)
534
        return pcm;
535

536
    ss << ":" << card;
537

538
    return pcm + ss.str();
539 540
}

541
namespace {
542 543 544
bool safeUpdate(snd_pcm_t *handle, int &samples)
{
    samples = snd_pcm_avail_update(handle);
545

546 547
    if (samples < 0) {
        samples = snd_pcm_recover(handle, samples, 0);
548

549 550 551 552 553
        if (samples < 0) {
            ERROR("Got unrecoverable error from snd_pcm_avail_update: %s", snd_strerror(samples));
            return false;
        }
    }
554

555 556 557
    return true;
}

558
std::vector<std::string>
559
getValues(const std::vector<HwIDPair> &deviceMap)
560
{
561
    std::vector<std::string> audioDeviceList;
562

563 564
    for (const auto &dev : deviceMap)
        audioDeviceList.push_back(dev.second);
565

566 567
    return audioDeviceList;
}
568
}
569

570 571 572 573 574 575 576 577 578 579 580
std::vector<std::string>
AlsaLayer::getCaptureDeviceList() const
{
    return getValues(getAudioDeviceIndexMap(true));
}

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

std::vector<HwIDPair>
583
AlsaLayer::getAudioDeviceIndexMap(bool getCapture) const
584
{
585 586 587
    snd_ctl_t* handle;
    snd_ctl_card_info_t *info;
    snd_pcm_info_t* pcminfo;
588 589
    snd_ctl_card_info_alloca(&info);
    snd_pcm_info_alloca(&pcminfo);
590

591
    int numCard = -1;
592

593
    std::vector<HwIDPair> audioDevice;
594 595

    if (snd_card_next(&numCard) < 0 || numCard < 0)
596
        return audioDevice;
597

598
    do {
599 600
        std::stringstream ss;
        ss << numCard;
601
        std::string name = "hw:" + ss.str();
602

603 604 605
        if (snd_ctl_open(&handle, name.c_str(), 0) == 0) {
            if (snd_ctl_card_info(handle, info) == 0) {
                snd_pcm_info_set_device(pcminfo , 0);
606
                snd_pcm_info_set_stream(pcminfo, getCapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
607

608 609 610
                int err;
                if ((err = snd_ctl_pcm_info(handle ,pcminfo)) < 0) {
                    WARN("Cannot get info: %s", snd_strerror(err));
611
                } else {
612
                    DEBUG("card %i : %s [%s]",
613 614 615
                          numCard,
                          snd_ctl_card_info_get_id(info),
                          snd_ctl_card_info_get_name(info));
616 617 618
                    std::string description = snd_ctl_card_info_get_name(info);
                    description.append(" - ");
                    description.append(snd_pcm_info_get_name(pcminfo));
619

620
                    // The number of the sound card is associated with a string description
621
                    audioDevice.push_back(HwIDPair(numCard , description));
622 623
                }
            }
624

625
            snd_ctl_close(handle);
626
        }
627
    } while (snd_card_next(&numCard) >= 0 && numCard >= 0);
628 629


630
    return audioDevice;
631 632 633
}


634
bool
635
AlsaLayer::soundCardIndexExists(int card, PCMType stream)
636
{
637
    snd_pcm_info_t *pcminfo;
638 639
    snd_pcm_info_alloca(&pcminfo);
    std::string name("hw:");
640
    std::stringstream ss;
641 642
    ss << card;
    name.append(ss.str());
643

644
    snd_ctl_t* handle;
645

646
    if (snd_ctl_open(&handle, name.c_str(), 0) != 0)
647
        return false;
648

649
    snd_pcm_info_set_stream(pcminfo , (stream == SFL_PCM_PLAYBACK) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE);
650 651 652
    bool ret = snd_ctl_pcm_info(handle , pcminfo) >= 0;
    snd_ctl_close(handle);
    return ret;
653
}
654

655
int
656
AlsaLayer::getAudioDeviceIndex(const std::string &description) const
657
{
658 659
    std::vector<HwIDPair> captureDevice(getAudioDeviceIndexMap(true));
    std::vector<HwIDPair> playbackDevice(getAudioDeviceIndexMap(false));
660

661
    std::vector<HwIDPair> audioDeviceIndexMap;
662 663 664
    audioDeviceIndexMap.insert(audioDeviceIndexMap.end(), captureDevice.begin(), captureDevice.end());
    audioDeviceIndexMap.insert(audioDeviceIndexMap.end(), playbackDevice.begin(), playbackDevice.end());

665 666 667
    for (const auto &dev : audioDeviceIndexMap)
        if (dev.second == description)
            return dev.first;
668

669 670
    // else return the default one
    return 0;
671 672
}

673 674 675 676 677 678 679 680 681 682
std::string
AlsaLayer::getAudioDeviceName(int index, PCMType type) const
{
    // 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) {
        case SFL_PCM_PLAYBACK:
        case SFL_PCM_RINGTONE:
            return getPlaybackDeviceList().at(index);
683

684 685
        case SFL_PCM_CAPTURE:
            return getCaptureDeviceList().at(index);
686

687 688 689 690 691 692
        default:
            ERROR("Unexpected type %d", type);
            return "";
    }
}

693
void AlsaLayer::capture()
694
{
695
    unsigned int mainBufferSampleRate = Manager::instance().getMainBuffer().getInternalSamplingRate();
696
    bool resample = sampleRate_ != mainBufferSampleRate;
697

698 699
    int toGetSamples = snd_pcm_avail_update(captureHandle_);

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
700
    if (toGetSamples < 0)
701
        ERROR("Audio: Mic error: %s", snd_strerror(toGetSamples));
702

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
703 704 705 706
    if (toGetSamples <= 0)
        return;

    const int framesPerBufferAlsa = 2048;
707
    toGetSamples = std::min(framesPerBufferAlsa, toGetSamples);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
708

709
    AudioBuffer in(toGetSamples, 1, sampleRate_);
710

711
    // TODO: handle ALSA multichannel capture
712
    const int toGetBytes = in.frames() * sizeof(SFLAudioSample);
713
    SFLAudioSample * const in_ptr = in.getChannel(0)->data();
714

715
    if (read(in_ptr, toGetBytes) != toGetBytes) {
716
        ERROR("ALSA MIC : Couldn't read!");
Tristan Matthews's avatar
Tristan Matthews committed
717
        return;
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
718
    }
719

720
    in.applyGain(captureGain_);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
721 722

    if (resample) {
723
        int outSamples = toGetSamples * (static_cast<double>(sampleRate_) / mainBufferSampleRate);
724
        AudioBuffer rsmpl_out(outSamples, 1, mainBufferSampleRate);
725 726 727
        converter_.resample(in, rsmpl_out);
        dcblocker_.process(rsmpl_out);
        Manager::instance().getMainBuffer().putData(rsmpl_out, MainBuffer::DEFAULT_ID);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
728
    } else {
729 730
        dcblocker_.process(in);
        Manager::instance().getMainBuffer().putData(in, MainBuffer::DEFAULT_ID);
Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
731 732
    }
}
733

Rafaël Carré's avatar
* #6629  
Rafaël Carré committed
734 735
void AlsaLayer::playback(int maxSamples)
{
736
    size_t bytesToGet = Manager::instance().getMainBuffer().availableForGet(MainBuffer::DEFAULT_ID);
Emmanuel Milou's avatar
Emmanuel Milou committed
737

738
    const size_t bytesToPut = maxSamples * sizeof(SFLAudioSample);
739

740 741
    // no audio available, play tone or silence
    if (bytesToGet <= 0) {
742 743
        // 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
744 745
        AudioLoop *tone = Manager::instance().getTelephoneTone();
        AudioLoop *file_tone = Manager::instance().getTelephoneFile();
746

747
        AudioBuffer out(maxSamples, 1, sampleRate_);
748

749
        if (tone)
750
            tone->getNext(out, playbackGain_);
751
        else if (file_tone && !ringtoneHandle_)
752
            file_tone->getNext(out, playbackGain_);
753

754
        write(out.getChannel(0)->data(), bytesToPut, playbackHandle_);
755 756
    } else {
        // play the regular sound samples
757

758
        const size_t mainBufferSampleRate = Manager::instance().getMainBuffer().getInternalSamplingRate();
759
        const bool resample = sampleRate_ != mainBufferSampleRate;
760

761 762
        double resampleFactor = 1.0;
        size_t maxNbBytesToGet = bytesToPut;
763

764 765 766 767
        if (resample) {
            resampleFactor = static_cast<double>(sampleRate_) / mainBufferSampleRate;
            maxNbBytesToGet = bytesToGet / resampleFactor;
        }
768

769 770
        bytesToGet = std::min(maxNbBytesToGet, bytesToGet);

771
        const size_t samplesToGet = bytesToGet / sizeof(SFLAudioSample);
772
        //std::vector<SFLAudioSample> out(samplesToGet, 0);
Adrien Béraud's avatar
Adrien Béraud committed
773
        AudioBuffer out(samplesToGet, 1, mainBufferSampleRate);
774

775
        Manager::instance().getMainBuffer().getData(out, MainBuffer::DEFAULT_ID);
776
        out.applyGain(playbackGain_);
777 778 779

        if (resample) {
            const size_t outSamples = samplesToGet * resampleFactor;