| // 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 |
| |