OLD | NEW |
1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // Implementation of audio output using the Linux ALSA interface. | 5 // Implementation of audio output using the Linux ALSA interface. |
6 | 6 |
7 #include <fcntl.h> | 7 #include <fcntl.h> |
8 #include <stdio.h> | 8 #include <stdio.h> |
9 | 9 |
10 #include <alsa/asoundlib.h> | 10 #include <alsa/asoundlib.h> |
11 | 11 |
12 #include "audio_output.h" | 12 #include "audio_output.h" |
13 #include "log.h" | 13 #include "log.h" |
14 #include "threading.h" | 14 #include "threading.h" |
15 | 15 |
| 16 namespace { |
| 17 |
| 18 bool isDeviceReady() { |
| 19 snd_ctl_card_info_t *card_info; |
| 20 snd_ctl_card_info_alloca(&card_info); |
| 21 |
| 22 snd_pcm_info_t *pcm_info; |
| 23 snd_pcm_info_alloca(&pcm_info); |
| 24 |
| 25 int valid_playback_devices = 0; |
| 26 int card_index = -1; |
| 27 |
| 28 while(snd_card_next(&card_index) == 0 && card_index >= 0) { |
| 29 char card_name[20]; |
| 30 snprintf(card_name, sizeof(card_name), "hw:%d", card_index); |
| 31 LOG(INFO) << "Checking ALSA sound card " << card_name; |
| 32 |
| 33 snd_ctl_t *ctl; |
| 34 if(snd_ctl_open(&ctl, card_name, 0) < 0) |
| 35 continue; |
| 36 |
| 37 snd_ctl_card_info(ctl, card_info); |
| 38 |
| 39 int dev_index = -1; |
| 40 while (snd_ctl_pcm_next_device(ctl, &dev_index) == 0 && dev_index >= 0) { |
| 41 char device_name[30]; |
| 42 snprintf(device_name, sizeof(device_name), |
| 43 "hw:%d,%d", card_index, dev_index); |
| 44 LOG(INFO) << "Checking ALSA sound device " << device_name; |
| 45 |
| 46 /* Obtain info about this particular device */ |
| 47 snd_pcm_info_set_device(pcm_info, dev_index); |
| 48 snd_pcm_info_set_subdevice(pcm_info, 0); |
| 49 snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_PLAYBACK); |
| 50 if (snd_ctl_pcm_info(ctl, pcm_info) >= 0) { |
| 51 LOG(INFO) << " Valid playback device: " << device_name; |
| 52 valid_playback_devices++; |
| 53 } |
| 54 } |
| 55 snd_ctl_close(ctl); |
| 56 } |
| 57 |
| 58 LOG(INFO) << "Total valid playback devices: " << valid_playback_devices; |
| 59 if (valid_playback_devices == 0) { |
| 60 return false; |
| 61 } |
| 62 return true; |
| 63 } |
| 64 |
| 65 } |
| 66 |
16 namespace speech_synthesis { | 67 namespace speech_synthesis { |
17 | 68 |
18 class LinuxAlsaAudioOutput : public AudioOutput, public Runnable { | 69 class LinuxAlsaAudioOutput : public AudioOutput, public Runnable { |
19 public: | 70 public: |
20 explicit LinuxAlsaAudioOutput(Threading* threading) | 71 explicit LinuxAlsaAudioOutput(Threading* threading) |
21 : threading_(threading), | 72 : threading_(threading), |
22 provider_(NULL) { | 73 provider_(NULL) { |
23 } | 74 } |
24 | 75 |
25 bool Init(AudioProvider *provider) { | 76 bool Init(AudioProvider *provider) { |
26 if (!provider) { | 77 if (!provider) { |
27 LOG(ERROR) << "An AudioProvider is required.\n"; | 78 LOG(ERROR) << "An AudioProvider is required.\n"; |
28 return false; | 79 return false; |
29 } | 80 } |
30 | 81 |
31 if (provider_) { | 82 if (provider_) { |
32 return true; | 83 return true; |
33 } | 84 } |
34 | 85 |
| 86 if (!isDeviceReady()) { |
| 87 return false; |
| 88 } |
| 89 |
35 int err; | 90 int err; |
36 if ((err = snd_pcm_open(&pcm_out_handle_, | 91 if ((err = snd_pcm_open(&pcm_out_handle_, |
37 "plughw:0,0", | 92 "default", |
38 SND_PCM_STREAM_PLAYBACK, | 93 SND_PCM_STREAM_PLAYBACK, |
39 0)) < 0) { | 94 0)) < 0) { |
40 LOG(INFO) << "Can't open wave output: " << snd_strerror(err) << "\n"; | 95 LOG(INFO) << "Can't open wave output: " << snd_strerror(err) << "\n"; |
41 return false; | 96 return false; |
42 } | 97 } |
43 | 98 |
44 snd_pcm_hw_params_t *hw_params; | 99 sample_rate_ = 44100; |
45 if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { | 100 channel_count_ = 1; |
46 LOG(INFO) << "Can't alloc sound hardware struct " << | 101 int soft_resample = 1; |
47 snd_strerror(err) << "\n"; | 102 unsigned int latency_us = 50000; |
| 103 if ((err = snd_pcm_set_params(pcm_out_handle_, |
| 104 SND_PCM_FORMAT_S16_LE, |
| 105 SND_PCM_ACCESS_RW_INTERLEAVED, |
| 106 channel_count_, |
| 107 sample_rate_, |
| 108 soft_resample, |
| 109 latency_us)) < 0) { |
| 110 LOG(INFO) << "Can't set pcm parameters " << snd_strerror(err); |
48 return false; | 111 return false; |
49 } | 112 } |
50 | 113 |
51 if ((err = snd_pcm_hw_params_any(pcm_out_handle_, hw_params)) < 0) { | 114 snd_pcm_uframes_t buffer_size = 0; |
52 LOG(INFO) << "Can't init sound hardware struct: " << | 115 snd_pcm_uframes_t period_size = 0; |
53 snd_strerror(err) << "\n"; | 116 if ((err = snd_pcm_get_params(pcm_out_handle_, &buffer_size, &period_size)) |
| 117 < 0) { |
| 118 LOG(INFO) << "Can't get pcm parameters: " << snd_strerror(err); |
54 return false; | 119 return false; |
55 } | 120 } |
56 | 121 |
57 if ((err = snd_pcm_hw_params_set_access( | 122 chunk_size_ = period_size; |
58 pcm_out_handle_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { | 123 total_buffer_size_ = buffer_size; |
59 LOG(INFO) << "Can't set access: " << snd_strerror(err) << "\n"; | |
60 return false; | |
61 } | |
62 | |
63 if ((err = snd_pcm_hw_params_set_format(pcm_out_handle_, | |
64 hw_params, | |
65 SND_PCM_FORMAT_S16_LE)) < 0) { | |
66 LOG(INFO) << "Can't set 16-bit: " << snd_strerror(err) << "\n"; | |
67 return false; | |
68 } | |
69 | |
70 if ((err = snd_pcm_hw_params_set_rate(pcm_out_handle_, | |
71 hw_params, | |
72 44100, | |
73 0)) < 0) { | |
74 LOG(INFO) << "Can't set rate to 44100: " << snd_strerror(err) << "\n"; | |
75 return false; | |
76 } | |
77 | |
78 if ((err = snd_pcm_hw_params_set_channels(pcm_out_handle_, | |
79 hw_params, | |
80 1)) < 0) { | |
81 LOG(INFO) << "Can't set channels to 1: " << snd_strerror(err) << "\n"; | |
82 return false; | |
83 } | |
84 | |
85 int dir = 0; | |
86 snd_pcm_uframes_t desired_period = 512; | |
87 if ((err = snd_pcm_hw_params_set_period_size_near( | |
88 pcm_out_handle_, hw_params, &desired_period, &dir)) < 0) { | |
89 LOG(INFO) << "Can't set period size: " << snd_strerror(err) << "\n"; | |
90 return false; | |
91 } | |
92 | |
93 snd_pcm_uframes_t period_size; | |
94 snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir); | |
95 chunk_size_ = static_cast<int>(period_size); | |
96 | |
97 snd_pcm_uframes_t desired_buffer_size = period_size * 4; | |
98 if ((err = snd_pcm_hw_params_set_buffer_size( | |
99 pcm_out_handle_, hw_params, desired_buffer_size)) < 0) { | |
100 LOG(INFO) << "Can't set buffer size: " << snd_strerror(err) << "\n"; | |
101 return false; | |
102 } | |
103 | |
104 snd_pcm_uframes_t buffer_size; | |
105 snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size); | |
106 total_buffer_size_ = static_cast<int>(buffer_size); | |
107 | |
108 unsigned int rate; | |
109 snd_pcm_hw_params_get_rate(hw_params, &rate, &dir); | |
110 sample_rate_ = rate; | |
111 | |
112 unsigned int channels; | |
113 snd_pcm_hw_params_get_channels(hw_params, &channels); | |
114 channel_count_ = channels; | |
115 | |
116 if ((err = snd_pcm_hw_params(pcm_out_handle_, hw_params)) < 0) { | |
117 LOG(INFO) << "Can't set hardware params: " << snd_strerror(err) << "\n"; | |
118 return false; | |
119 } | |
120 | |
121 snd_pcm_hw_params_free(hw_params); | |
122 | 124 |
123 if ((err = snd_pcm_prepare(pcm_out_handle_)) < 0) { | 125 if ((err = snd_pcm_prepare(pcm_out_handle_)) < 0) { |
124 LOG(INFO) << "Can't prepare: " << snd_strerror(err) << "\n"; | 126 LOG(INFO) << "Can't prepare: " << snd_strerror(err) << "\n"; |
125 return false; | 127 return false; |
126 } | 128 } |
127 | 129 |
128 keep_running_ = true; | 130 keep_running_ = true; |
129 provider_ = provider; | 131 provider_ = provider; |
130 return true; | 132 return true; |
131 } | 133 } |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 if (!provider_->FillAudioBuffer(chunk, chunk_size)) { | 180 if (!provider_->FillAudioBuffer(chunk, chunk_size)) { |
179 LOG(INFO) << "Could not fill audio buffer\n"; | 181 LOG(INFO) << "Could not fill audio buffer\n"; |
180 break; | 182 break; |
181 } | 183 } |
182 | 184 |
183 int err = snd_pcm_writei(pcm_out_handle_, | 185 int err = snd_pcm_writei(pcm_out_handle_, |
184 static_cast<void *>(chunk), | 186 static_cast<void *>(chunk), |
185 static_cast<snd_pcm_uframes_t>(chunk_size)); | 187 static_cast<snd_pcm_uframes_t>(chunk_size)); |
186 if (err < 0) { | 188 if (err < 0) { |
187 LOG(INFO) << "Write error: " << err << snd_strerror(err) << "\n"; | 189 LOG(INFO) << "Write error: " << err << snd_strerror(err) << "\n"; |
188 return; | 190 do { |
| 191 sleep(1); |
| 192 LOG(INFO) << "Attempting to recover audio"; |
| 193 } while (keep_running_ && |
| 194 0 != snd_pcm_recover(pcm_out_handle_, err, 0)); |
189 } | 195 } |
190 } | 196 } |
191 | 197 |
192 delete[] chunk; | 198 delete[] chunk; |
193 } | 199 } |
194 | 200 |
195 private: | 201 private: |
196 volatile bool keep_running_; | 202 volatile bool keep_running_; |
197 Threading* threading_; | 203 Threading* threading_; |
198 AudioProvider* provider_; | 204 AudioProvider* provider_; |
199 Thread* thread_; | 205 Thread* thread_; |
200 snd_pcm_t *pcm_out_handle_; | 206 snd_pcm_t *pcm_out_handle_; |
201 int sample_rate_; | 207 int sample_rate_; |
202 int channel_count_; | 208 int channel_count_; |
203 int chunk_size_; | 209 int chunk_size_; |
204 int total_buffer_size_; | 210 int total_buffer_size_; |
205 }; | 211 }; |
206 | 212 |
207 AudioOutput* AudioOutput::Create(Threading* threading) { | 213 AudioOutput* AudioOutput::Create(Threading* threading) { |
208 return new LinuxAlsaAudioOutput(threading); | 214 return new LinuxAlsaAudioOutput(threading); |
209 } | 215 } |
210 | 216 |
211 } // namespace speech_synthesis | 217 } // namespace speech_synthesis |
212 | 218 |
OLD | NEW |