blob: d2563612e1a262f4c6220328eb7936d830827622 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Implementation of audio output using the Linux ALSA interface.
#include <fcntl.h>
#include <stdio.h>
#include <vector>
#include <alsa/asoundlib.h>
#include "base/memory/scoped_ptr.h"
#include "audio_output.h"
#include "log.h"
#include "threading.h"
namespace speech_synthesis {
namespace {
const int kSampleRate = 44100;
const int kChannelCount = 1;
bool isDeviceReady() {
snd_ctl_card_info_t *card_info;
snd_ctl_card_info_alloca(&card_info);
snd_pcm_info_t *pcm_info;
snd_pcm_info_alloca(&pcm_info);
int valid_playback_devices = 0;
int card_index = -1;
while(snd_card_next(&card_index) == 0 && card_index >= 0) {
char card_name[20];
snprintf(card_name, sizeof(card_name), "hw:%d", card_index);
LOG(INFO) << "Checking ALSA sound card " << card_name;
snd_ctl_t *ctl;
if(snd_ctl_open(&ctl, card_name, 0) < 0)
continue;
snd_ctl_card_info(ctl, card_info);
int dev_index = -1;
while (snd_ctl_pcm_next_device(ctl, &dev_index) == 0 && dev_index >= 0) {
char device_name[30];
snprintf(device_name, sizeof(device_name),
"hw:%d,%d", card_index, dev_index);
LOG(INFO) << "Checking ALSA sound device " << device_name;
/* Obtain info about this particular device */
snd_pcm_info_set_device(pcm_info, dev_index);
snd_pcm_info_set_subdevice(pcm_info, 0);
snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_PLAYBACK);
if (snd_ctl_pcm_info(ctl, pcm_info) >= 0) {
LOG(INFO) << " Valid playback device: " << device_name;
valid_playback_devices++;
}
}
snd_ctl_close(ctl);
}
LOG(INFO) << "Total valid playback devices: " << valid_playback_devices;
if (valid_playback_devices == 0) {
return false;
}
return true;
}
class LinuxAlsaAudioOutput : public AudioOutput, public Runnable {
public:
LinuxAlsaAudioOutput()
: keep_running_(false),
provider_(NULL),
pcm_out_handle_(NULL),
sample_rate_(0),
channel_count_(0),
chunk_size_(0),
total_buffer_size_(0) {
}
virtual ~LinuxAlsaAudioOutput() {
StopAudio();
}
// AudioOutput overrides:
virtual bool Init(AudioProvider *provider) OVERRIDE {
if (!provider) {
LOG(ERROR) << "An AudioProvider is required.\n";
return false;
}
if (provider_) {
return true;
}
if (!isDeviceReady()) {
return false;
}
int err = 0;
if ((err = snd_pcm_open(&pcm_out_handle_,
"default",
SND_PCM_STREAM_PLAYBACK,
0)) < 0) {
LOG(INFO) << "Can't open wave output: " << snd_strerror(err) << "\n";
return false;
}
sample_rate_ = kSampleRate;
channel_count_ = kChannelCount;
int soft_resample = 1;
unsigned int latency_us = 50000;
if ((err = snd_pcm_set_params(pcm_out_handle_,
SND_PCM_FORMAT_S16_LE,
SND_PCM_ACCESS_RW_INTERLEAVED,
channel_count_,
sample_rate_,
soft_resample,
latency_us)) < 0) {
LOG(INFO) << "Can't set pcm parameters " << snd_strerror(err);
return false;
}
snd_pcm_uframes_t buffer_size = 0;
snd_pcm_uframes_t period_size = 0;
if ((err = snd_pcm_get_params(pcm_out_handle_, &buffer_size, &period_size))
< 0) {
LOG(INFO) << "Can't get pcm parameters: " << snd_strerror(err);
return false;
}
chunk_size_ = period_size;
total_buffer_size_ = buffer_size;
if ((err = snd_pcm_prepare(pcm_out_handle_)) < 0) {
LOG(INFO) << "Can't prepare: " << snd_strerror(err) << "\n";
return false;
}
keep_running_ = true;
provider_ = provider;
return true;
}
virtual void StartAudio() OVERRIDE {
thread_.reset(Thread::StartJoinableThread(this));
}
virtual void StopAudio() OVERRIDE {
keep_running_ = false;
if (thread_.get()) {
thread_->Join();
thread_.reset();
}
if (pcm_out_handle_) {
snd_pcm_close(pcm_out_handle_);
pcm_out_handle_ = NULL;
}
}
virtual int GetSampleRate() OVERRIDE {
return kSampleRate;
}
virtual int GetChannelCount() OVERRIDE {
return kChannelCount;
}
virtual int GetChunkSize() OVERRIDE {
return chunk_size_;
}
virtual int GetTotalBufferSize() OVERRIDE {
return total_buffer_size_;
}
// Runnable override.
virtual void Run() OVERRIDE {
const int chunk_size = GetChunkSize();
std::vector<int16_t> chunk(chunk_size);
if (!provider_) {
LOG(ERROR) << "Error: audio output was never initialized.\n";
return;
}
LOG(INFO) << "In audio thread\n";
while (keep_running_) {
if (!provider_->FillAudioBuffer(&chunk[0], chunk_size)) {
LOG(INFO) << "Could not fill audio buffer\n";
break;
}
int err = snd_pcm_writei(pcm_out_handle_,
&chunk[0],
static_cast<snd_pcm_uframes_t>(chunk_size));
if (err < 0) {
LOG(INFO) << "Write error: " << err << snd_strerror(err) << "\n";
do {
sleep(1);
LOG(INFO) << "Attempting to recover audio";
} while (keep_running_ &&
0 != snd_pcm_recover(pcm_out_handle_, err, 0));
}
}
}
private:
volatile bool keep_running_;
AudioProvider* provider_;
scoped_ptr<Thread> thread_;
snd_pcm_t *pcm_out_handle_;
int sample_rate_;
int channel_count_;
int chunk_size_;
int total_buffer_size_;
DISALLOW_COPY_AND_ASSIGN(LinuxAlsaAudioOutput);
};
} // namespace
AudioOutput* AudioOutput::Create() {
return new LinuxAlsaAudioOutput();
}
} // namespace speech_synthesis