blob: fdf4c6e811c63c7b0c6aea49fa17b7f7662fe98b [file] [log] [blame]
// Copyright (c) 2010 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 <alsa/asoundlib.h>
#include "audio_output.h"
#include "log.h"
#include "threading.h"
namespace {
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;
}
}
namespace speech_synthesis {
class LinuxAlsaAudioOutput : public AudioOutput, public Runnable {
public:
explicit LinuxAlsaAudioOutput(Threading* threading)
: threading_(threading),
provider_(NULL) {
}
bool Init(AudioProvider *provider) {
if (!provider) {
LOG(ERROR) << "An AudioProvider is required.\n";
return false;
}
if (provider_) {
return true;
}
if (!isDeviceReady()) {
return false;
}
int err;
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_ = 44100;
channel_count_ = 1;
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 ~LinuxAlsaAudioOutput() {
StopAudio();
}
virtual void StartAudio() {
thread_ = threading_->StartJoinableThread(this);
}
virtual void StopAudio() {
keep_running_ = false;
if (thread_) {
thread_->Join();
thread_ = NULL;
}
snd_pcm_close(pcm_out_handle_);
}
virtual int GetSampleRate() {
return 44100;
}
virtual int GetChannelCount() {
return 1;
}
virtual int GetChunkSize() {
return chunk_size_;
}
virtual int GetTotalBufferSize() {
return total_buffer_size_;
}
virtual void Run() {
const int chunk_size = GetChunkSize();
int16_t* chunk = new int16_t[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, chunk_size)) {
LOG(INFO) << "Could not fill audio buffer\n";
break;
}
int err = snd_pcm_writei(pcm_out_handle_,
static_cast<void *>(chunk),
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));
}
}
delete[] chunk;
}
private:
volatile bool keep_running_;
Threading* threading_;
AudioProvider* provider_;
Thread* thread_;
snd_pcm_t *pcm_out_handle_;
int sample_rate_;
int channel_count_;
int chunk_size_;
int total_buffer_size_;
};
AudioOutput* AudioOutput::Create(Threading* threading) {
return new LinuxAlsaAudioOutput(threading);
}
} // namespace speech_synthesis