Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(985)

Side by Side Diff: services/media/audio/platform/linux/alsa_output.cc

Issue 1419593007: Add an ALSA output to the motown audio server. (Closed) Base URL: https://github.com/domokit/mojo.git@change5
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « services/media/audio/platform/linux/alsa_output.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <limits>
6 #include <set>
7
8 #include "mojo/services/media/common/cpp/local_time.h"
9 #include "services/media/audio/platform/linux/alsa_output.h"
10
11 namespace mojo {
12 namespace media {
13 namespace audio {
14
15 static constexpr LocalDuration TARGET_LATENCY = local_time::from_msec(35);
16 static constexpr LocalDuration LOW_BUF_THRESH = local_time::from_msec(30);
17 static constexpr LocalDuration ERROR_RECOVERY_TIME = local_time::from_msec(300);
18 static constexpr LocalDuration WAIT_FOR_ALSA_DELAY = local_time::from_usec(500);
19 static const std::set<uint8_t> SUPPORTED_CHANNEL_COUNTS({ 1, 2 });
20 static const std::set<uint32_t> SUPPORTED_SAMPLE_RATES({
21 48000, 32000, 24000, 16000, 8000, 4000,
22 44100, 22050, 11025,
23 });
24
25 static inline bool IsRecoverableAlsaError(int error_code) {
26 switch (error_code) {
27 case -EINTR:
28 case -EPIPE:
29 case -ESTRPIPE:
30 return true;
31 default:
32 return false;
33 }
34 }
35
36 AlsaOutput::AlsaOutput(AudioOutputManager* manager)
37 : StandardOutputBase(manager) {}
38
39 AlsaOutput::~AlsaOutput() {
40 // We should have been cleaned up already, but in release builds, call cleanup
41 // anyway, just in case something got missed.
42 DCHECK(!alsa_device_);
43 Cleanup();
44 }
45
46 AudioOutputPtr AlsaOutput::New(AudioOutputManager* manager) {
47 return AudioOutputPtr(new AlsaOutput(manager));
48 }
49
50 MediaResult AlsaOutput::Configure(LpcmMediaTypeDetailsPtr config) {
51 if (!config) { return MediaResult::INVALID_ARGUMENT; }
52 if (output_format_) { return MediaResult::BAD_STATE; }
53
54 uint32_t bytes_per_sample;
55 switch (config->sample_format) {
56 case LpcmSampleFormat::UNSIGNED_8:
57 alsa_format_ = SND_PCM_FORMAT_U8;
58 silence_byte_ = 0x80;
59 bytes_per_sample = 1;
60 break;
61
62 case LpcmSampleFormat::SIGNED_16:
63 alsa_format_ = SND_PCM_FORMAT_S16;
64 silence_byte_ = 0x00;
65 bytes_per_sample = 2;
66 break;
67
68 case LpcmSampleFormat::SIGNED_24_IN_32:
69 default:
70 return MediaResult::UNSUPPORTED_CONFIG;
71 }
72
73 if (SUPPORTED_SAMPLE_RATES.find(config->frames_per_second) ==
74 SUPPORTED_SAMPLE_RATES.end()) {
75 return MediaResult::UNSUPPORTED_CONFIG;
76 }
77
78 if (SUPPORTED_CHANNEL_COUNTS.find(config->samples_per_frame) ==
79 SUPPORTED_CHANNEL_COUNTS.end()) {
80 return MediaResult::UNSUPPORTED_CONFIG;
81 }
82
83 // Compute the ratio between frames and local time ticks.
84 LinearTransform::Ratio sec_per_tick(LocalDuration::period::num,
85 LocalDuration::period::den);
86 LinearTransform::Ratio frames_per_sec(config->frames_per_second, 1);
87 bool is_precise = LinearTransform::Ratio::Compose(frames_per_sec,
88 sec_per_tick,
89 &frames_per_tick_);
90 DCHECK(is_precise);
91
92 // Figure out how many bytes there are per frame.
93 output_bytes_per_frame_ = bytes_per_sample * config->samples_per_frame;
94
95 // Success
96 output_format_ = config.Pass();
97 return MediaResult::OK;
98 }
99
100 MediaResult AlsaOutput::Init() {
101 static const char* kAlsaDevice = "default";
102
103 if (!output_format_) { return MediaResult::BAD_STATE; }
104 if (alsa_device_) { return MediaResult::BAD_STATE; }
105
106 snd_pcm_sframes_t res;
107 res = snd_pcm_open(&alsa_device_,
108 kAlsaDevice,
109 SND_PCM_STREAM_PLAYBACK,
110 SND_PCM_NONBLOCK);
111 if (res != 0) {
112 LOG(ERROR) << "Failed to open ALSA device \"" << kAlsaDevice << "\".";
113 return MediaResult::INTERNAL_ERROR;
114 }
115
116 res = snd_pcm_set_params(alsa_device_,
117 alsa_format_,
118 SND_PCM_ACCESS_RW_INTERLEAVED,
119 output_format_->samples_per_frame,
120 output_format_->frames_per_second,
121 0, // do not allow ALSA resample
122 local_time::to_usec<unsigned int>(TARGET_LATENCY));
123 if (res) {
124 LOG(ERROR) << "Failed to configure ALSA device \"" << kAlsaDevice << "\" "
125 << "(res = " << res << ")";
126 LOG(ERROR) << "Requested samples per frame: "
127 << output_format_->samples_per_frame;
128 LOG(ERROR) << "Requested frames per second: "
129 << output_format_->frames_per_second;
130 LOG(ERROR) << "Requested ALSA format : " << alsa_format_;
131 Cleanup();
132 return MediaResult::INTERNAL_ERROR;
133 }
134
135 // Figure out how big our mixing buffer needs to be, then allocate it.
136 res = snd_pcm_avail_update(alsa_device_);
137 if (res <= 0) {
138 LOG(ERROR) << "[" << this << "] : "
139 << "Fatal error (" << res
140 << ") attempting to determine ALSA buffer size.";
141 Cleanup();
142 return MediaResult::INTERNAL_ERROR;
143 }
144
145 mix_buf_frames_ = res;
146 mix_buf_.reset(new uint8_t[mix_buf_frames_ * output_bytes_per_frame_]);
147
148 return MediaResult::OK;
149 }
150
151 void AlsaOutput::Cleanup() {
152 if (alsa_device_) {
153 snd_pcm_close(alsa_device_);
154 alsa_device_ = nullptr;
155 }
156
157 mix_buf_ = nullptr;
158 mix_buf_frames_ = 0;
159 }
160
161 bool AlsaOutput::StartMixJob(MixJob* job, const LocalTime& process_start) {
162 DCHECK(job);
163
164 // Are we not primed? If so, fill a mix buffer with silence and send it to
165 // the alsa device. Schedule a callback for a short time in the future so
166 // ALSA has a chance to start the output and we can take our best guess of the
167 // function which maps output frames to local time.
168 if (!primed_) {
169 HandleAsUnderflow();
170 return false;
171 }
172
173 // Figure out how many frames of audio we need to produce in order to top off
174 // the buffer. If we are primed, but do not know the transformation between
175 // audio frames and local time ticks, do our best to figure it out in the
176 // process.
177 snd_pcm_sframes_t avail;
178 if (!local_to_output_known_) {
179 snd_pcm_sframes_t delay;
180
181 int res = snd_pcm_avail_delay(alsa_device_, &avail, &delay);
182 LocalTime now = LocalClock::now();
183
184 if (res < 0) {
185 HandleAlsaError(res);
186 return false;
187 }
188
189 DCHECK_GE(delay, 0);
190 int64_t now_ticks = now.time_since_epoch().count();
191 local_to_output_ = LinearTransform(now_ticks, frames_per_tick_, -delay);
192 local_to_output_known_ = true;
193 frames_sent_ = 0;
194 while (++local_to_output_gen_ == MixJob::INVALID_GENERATION) {}
195 } else {
196 avail = snd_pcm_avail_update(alsa_device_);
197 if (avail < 0) {
198 HandleAlsaError(avail);
199 return false;
200 }
201 }
202
203 // Compute the time that we think we will completely underflow, then back off
204 // from that by the low buffer threshold and use that to determine when we
205 // should mix again.
206 int64_t playout_time_ticks;
207 bool trans_ok = local_to_output_.DoReverseTransform(frames_sent_,
208 &playout_time_ticks);
209 DCHECK(trans_ok);
210 LocalTime playout_time = LocalTime(LocalDuration(playout_time_ticks));
211 LocalTime low_buf_time = playout_time - LOW_BUF_THRESH;
212
213 if (process_start >= low_buf_time) {
214 // Because of the way that ALSA consumes data and updates its internal
215 // bookkeeping, it is possible that we are past our low buffer threshold,
216 // but ALSA still thinks that there is no room to write new frames. If this
217 // is the case, just try again a short amount of time in the future.
218 DCHECK_GE(avail, 0);
219 if (!avail) {
220 SetNextSchedDelay(WAIT_FOR_ALSA_DELAY);
221 return false;
222 }
223
224 // Limit the amt that we queue to be no more than what ALSA will currently
225 // accept, or what it currently will take to fill us to our target latency.
226 //
227 // The playout target had better be ahead of the playout time, or we are
228 // almost certainly going to underflow. If this happens, for whatever
229 // reason, just try to send a full buffer and deal with the underflow when
230 // ALSA notices it.
231 int64_t fill_amt;
232 LocalTime playout_target = LocalClock::now() + TARGET_LATENCY;
233 if (playout_target > playout_time) {
234 fill_amt = (playout_target - playout_time).count();
235 } else {
236 fill_amt = TARGET_LATENCY.count();
237 }
238
239 DCHECK_GE(fill_amt, 0);
240 DCHECK_LE(fill_amt, std::numeric_limits<int32_t>::max());
241 fill_amt *= frames_per_tick_.numerator;
242 fill_amt += frames_per_tick_.denominator - 1;
243 fill_amt /= frames_per_tick_.denominator;
244
245 job->buf_frames = (avail < fill_amt) ? avail : fill_amt;
246 if (job->buf_frames > mix_buf_frames_) {
247 job->buf_frames = mix_buf_frames_;
248 }
249
250 job->buf = mix_buf_.get();
251 job->start_pts_of = frames_sent_;
252 job->local_to_output = &local_to_output_;
253 job->local_to_output_gen = local_to_output_gen_;
254
255 // TODO(johngro): optimize this if we can. The first buffer we mix can just
256 // put its samples directly into the output buffer, and does not need to
257 // accumulate and clip. In theory, we only need to put silence in the
258 // places where our outputs are not going to already overwrite.
259 FillMixBufWithSilence(job->buf_frames);
260 return true;
261 }
262
263 // Wait until its time to mix some more data.
264 SetNextSchedTime(low_buf_time);
265 return false;
266 }
267
268 bool AlsaOutput::FinishMixJob(const MixJob& job) {
269 DCHECK(job.buf == mix_buf_.get());
270 DCHECK(job.buf_frames);
271
272 // We should always be able to write all of the data that we mixed.
273 snd_pcm_sframes_t res;
274 res = snd_pcm_writei(alsa_device_, job.buf, job.buf_frames);
275 if (res != job.buf_frames) {
276 HandleAlsaError(res);
277 return false;
278 }
279
280 frames_sent_ += res;
281 return true;
282 }
283
284 void AlsaOutput::FillMixBufWithSilence(uint32_t frames) {
285 DCHECK(mix_buf_);
286 DCHECK(frames <= mix_buf_frames_);
287
288 // TODO(johngro): someday, this may not be this simple. Filling unsigned
289 // multibyte sample formats, or floating point formats, will require something
290 // more sophisticated than filling with a single byte pattern.
291 ::memset(mix_buf_.get(), silence_byte_, frames * output_bytes_per_frame_);
292 }
293
294 void AlsaOutput::HandleAsUnderflow() {
295 snd_pcm_sframes_t res;
296
297 // If we were already primed, then this is a legitimate underflow, not the
298 // startup case or recovery from some other error.
299 if (primed_) {
300 // TODO(johngro): come up with a way to properly throttle this. Also, add a
301 // friendly name to the output so the log helps to identify which output
302 // underflowed.
303 LOG(WARNING) << "[" << this << "] : underflow";
304 res = snd_pcm_recover(alsa_device_, -EPIPE, true);
305 if (res < 0) {
306 HandleAsError(res);
307 return;
308 }
309 }
310
311 // TODO(johngro): We don't actually have to fill up the entire lead time with
312 // silence. When we have better control of our thread priorities, prime this
313 // with the minimimum amt we can get away with and still be able to start
314 // mixing without underflowing.
315 FillMixBufWithSilence(mix_buf_frames_);
316 res = snd_pcm_writei(alsa_device_, mix_buf_.get(), mix_buf_frames_);
317
318 if (res < 0) {
319 HandleAsError(res);
320 return;
321 }
322
323 primed_ = true;
324 local_to_output_known_ = false;
325 SetNextSchedDelay(local_time::from_msec(1));
326 }
327
328 void AlsaOutput::HandleAsError(snd_pcm_sframes_t code) {
329 // TODO(johngro): Throttle this somehow.
330 LOG(WARNING) << "[" << this << "] : Attempting to recover from ALSA error "
331 << code;
332
333 if (IsRecoverableAlsaError(code)) {
334 code = snd_pcm_recover(alsa_device_, code, true);
jeffbrown 2015/11/10 20:13:25 This could fail with EINTR too I think and the cor
johngro 2015/11/10 20:32:02 Docs are fuzzy on this issue. They basically say
335 if (!code) {
336 primed_ = false;
337 local_to_output_known_ = false;
338 SetNextSchedDelay(ERROR_RECOVERY_TIME);
339 }
340 }
341
342 LOG(ERROR) << "[" << this << "] : Fatal ALSA error "
343 << code << ". Shutting down";
344 ShutdownSelf();
345 }
346
347 void AlsaOutput::HandleAlsaError(snd_pcm_sframes_t code) {
348 // ALSA signals an underflow by returning -EPIPE from jobs. If the error code
349 // is -EPIPE, treat this as an underflow and attempt to reprime the pipeline.
350 if (code == -EPIPE) {
351 HandleAsUnderflow();
352 } else {
353 HandleAsError(code);
354 }
355 }
356
357 } // namespace audio
358 } // namespace media
359 } // namespace mojo
360
OLDNEW
« no previous file with comments | « services/media/audio/platform/linux/alsa_output.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698