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

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

Issue 1471813002: Mix to an intermediate buffer. (Closed) Base URL: https://github.com/domokit/mojo.git@change7
Patch Set: Final rebase before landing Created 4 years, 10 months 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
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 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 #include <limits> 5 #include <limits>
6 #include <set> 6 #include <set>
7 7
8 #include "mojo/services/media/common/cpp/local_time.h" 8 #include "mojo/services/media/common/cpp/local_time.h"
9 #include "services/media/audio/platform/linux/alsa_output.h" 9 #include "services/media/audio/platform/linux/alsa_output.h"
10 10
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
68 DCHECK(!alsa_device_); 68 DCHECK(!alsa_device_);
69 Cleanup(); 69 Cleanup();
70 } 70 }
71 71
72 AudioOutputPtr AlsaOutput::New(AudioOutputManager* manager) { 72 AudioOutputPtr AlsaOutput::New(AudioOutputManager* manager) {
73 return AudioOutputPtr(new AlsaOutput(manager)); 73 return AudioOutputPtr(new AlsaOutput(manager));
74 } 74 }
75 75
76 MediaResult AlsaOutput::Configure(LpcmMediaTypeDetailsPtr config) { 76 MediaResult AlsaOutput::Configure(LpcmMediaTypeDetailsPtr config) {
77 if (!config) { return MediaResult::INVALID_ARGUMENT; } 77 if (!config) { return MediaResult::INVALID_ARGUMENT; }
78 if (output_format_) { return MediaResult::BAD_STATE; } 78 if (output_formatter_) { return MediaResult::BAD_STATE; }
79 79
80 uint32_t bytes_per_sample; 80 output_formatter_ = OutputFormatter::Select(config);
81 if (!output_formatter_) { return MediaResult::UNSUPPORTED_CONFIG; }
82
81 switch (config->sample_format) { 83 switch (config->sample_format) {
82 case LpcmSampleFormat::UNSIGNED_8: 84 case LpcmSampleFormat::UNSIGNED_8:
83 alsa_format_ = SND_PCM_FORMAT_U8; 85 alsa_format_ = SND_PCM_FORMAT_U8;
84 silence_byte_ = 0x80;
85 bytes_per_sample = 1;
86 break; 86 break;
87 87
88 case LpcmSampleFormat::SIGNED_16: 88 case LpcmSampleFormat::SIGNED_16:
89 alsa_format_ = SND_PCM_FORMAT_S16; 89 alsa_format_ = SND_PCM_FORMAT_S16;
90 silence_byte_ = 0x00;
91 bytes_per_sample = 2;
92 break; 90 break;
93 91
94 case LpcmSampleFormat::SIGNED_24_IN_32: 92 case LpcmSampleFormat::SIGNED_24_IN_32:
95 default: 93 default:
96 return MediaResult::UNSUPPORTED_CONFIG; 94 return MediaResult::UNSUPPORTED_CONFIG;
97 } 95 }
98 96
99 if (SUPPORTED_SAMPLE_RATES.find(config->frames_per_second) == 97 if (SUPPORTED_SAMPLE_RATES.find(config->frames_per_second) ==
100 SUPPORTED_SAMPLE_RATES.end()) { 98 SUPPORTED_SAMPLE_RATES.end()) {
101 return MediaResult::UNSUPPORTED_CONFIG; 99 return MediaResult::UNSUPPORTED_CONFIG;
102 } 100 }
103 101
104 if (SUPPORTED_CHANNEL_COUNTS.find(config->channels) == 102 if (SUPPORTED_CHANNEL_COUNTS.find(config->channels) ==
105 SUPPORTED_CHANNEL_COUNTS.end()) { 103 SUPPORTED_CHANNEL_COUNTS.end()) {
106 return MediaResult::UNSUPPORTED_CONFIG; 104 return MediaResult::UNSUPPORTED_CONFIG;
107 } 105 }
108 106
109 // Compute the ratio between frames and local time ticks. 107 // Compute the ratio between frames and local time ticks.
110 LinearTransform::Ratio sec_per_tick(LocalDuration::period::num, 108 LinearTransform::Ratio sec_per_tick(LocalDuration::period::num,
111 LocalDuration::period::den); 109 LocalDuration::period::den);
112 LinearTransform::Ratio frames_per_sec(config->frames_per_second, 1); 110 LinearTransform::Ratio frames_per_sec(config->frames_per_second, 1);
113 bool is_precise = LinearTransform::Ratio::Compose(frames_per_sec, 111 bool is_precise = LinearTransform::Ratio::Compose(frames_per_sec,
114 sec_per_tick, 112 sec_per_tick,
115 &frames_per_tick_); 113 &frames_per_tick_);
116 DCHECK(is_precise); 114 DCHECK(is_precise);
117 115
118 // Figure out how many bytes there are per frame.
119 output_bytes_per_frame_ = bytes_per_sample * config->channels;
120
121 // Success 116 // Success
122 output_format_ = config.Pass();
123 return MediaResult::OK; 117 return MediaResult::OK;
124 } 118 }
125 119
126 MediaResult AlsaOutput::Init() { 120 MediaResult AlsaOutput::Init() {
127 static const char* kAlsaDevice = "default"; 121 static const char* kAlsaDevice = "default";
128 122
129 if (!output_format_) { return MediaResult::BAD_STATE; } 123 if (!output_formatter_) { return MediaResult::BAD_STATE; }
130 if (alsa_device_) { return MediaResult::BAD_STATE; } 124 if (alsa_device_) { return MediaResult::BAD_STATE; }
131 125
132 snd_pcm_sframes_t res; 126 snd_pcm_sframes_t res;
133 res = snd_pcm_open(&alsa_device_, 127 res = snd_pcm_open(&alsa_device_,
134 kAlsaDevice, 128 kAlsaDevice,
135 SND_PCM_STREAM_PLAYBACK, 129 SND_PCM_STREAM_PLAYBACK,
136 SND_PCM_NONBLOCK); 130 SND_PCM_NONBLOCK);
137 if (res != 0) { 131 if (res != 0) {
138 LOG(ERROR) << "Failed to open ALSA device \"" << kAlsaDevice << "\"."; 132 LOG(ERROR) << "Failed to open ALSA device \"" << kAlsaDevice << "\".";
139 return MediaResult::INTERNAL_ERROR; 133 return MediaResult::INTERNAL_ERROR;
140 } 134 }
141 135
142 res = snd_pcm_set_params(alsa_device_, 136 res = snd_pcm_set_params(alsa_device_,
143 alsa_format_, 137 alsa_format_,
144 SND_PCM_ACCESS_RW_INTERLEAVED, 138 SND_PCM_ACCESS_RW_INTERLEAVED,
145 output_format_->channels, 139 output_formatter_->format()->channels,
146 output_format_->frames_per_second, 140 output_formatter_->format()->frames_per_second,
147 0, // do not allow ALSA resample 141 0, // do not allow ALSA resample
148 local_time::to_usec<unsigned int>(TARGET_LATENCY)); 142 local_time::to_usec<unsigned int>(TARGET_LATENCY));
149 if (res) { 143 if (res) {
150 LOG(ERROR) << "Failed to configure ALSA device \"" << kAlsaDevice << "\" " 144 LOG(ERROR) << "Failed to configure ALSA device \"" << kAlsaDevice << "\" "
151 << "(res = " << res << ")"; 145 << "(res = " << res << ")";
152 LOG(ERROR) << "Requested channels : " 146 LOG(ERROR) << "Requested channels : "
153 << output_format_->channels; 147 << output_formatter_->format()->channels;
154 LOG(ERROR) << "Requested frames per second: " 148 LOG(ERROR) << "Requested frames per second: "
155 << output_format_->frames_per_second; 149 << output_formatter_->format()->frames_per_second;
156 LOG(ERROR) << "Requested ALSA format : " << alsa_format_; 150 LOG(ERROR) << "Requested ALSA format : " << alsa_format_;
157 Cleanup(); 151 Cleanup();
158 return MediaResult::INTERNAL_ERROR; 152 return MediaResult::INTERNAL_ERROR;
159 } 153 }
160 154
161 // Figure out how big our mixing buffer needs to be, then allocate it. 155 // Figure out how big our mixing buffer needs to be, then allocate it.
162 res = snd_pcm_avail_update(alsa_device_); 156 res = snd_pcm_avail_update(alsa_device_);
163 if (res <= 0) { 157 if (res <= 0) {
164 LOG(ERROR) << "[" << this << "] : " 158 LOG(ERROR) << "[" << this << "] : "
165 << "Fatal error (" << res 159 << "Fatal error (" << res
166 << ") attempting to determine ALSA buffer size."; 160 << ") attempting to determine ALSA buffer size.";
167 Cleanup(); 161 Cleanup();
168 return MediaResult::INTERNAL_ERROR; 162 return MediaResult::INTERNAL_ERROR;
169 } 163 }
170 164
165 size_t buffer_size;
171 mix_buf_frames_ = res; 166 mix_buf_frames_ = res;
172 mix_buf_.reset(new uint8_t[mix_buf_frames_ * output_bytes_per_frame_]); 167 buffer_size = mix_buf_frames_ * output_formatter_->bytes_per_frame();
168 mix_buf_.reset(new uint8_t[buffer_size]);
169
170 // Set up the intermediate buffer at the StandardOutputBase level
171 SetupMixBuffer(mix_buf_frames_);
173 172
174 return MediaResult::OK; 173 return MediaResult::OK;
175 } 174 }
176 175
177 void AlsaOutput::Cleanup() { 176 void AlsaOutput::Cleanup() {
178 if (alsa_device_) { 177 if (alsa_device_) {
179 snd_pcm_close(alsa_device_); 178 snd_pcm_close(alsa_device_);
180 alsa_device_ = nullptr; 179 alsa_device_ = nullptr;
181 } 180 }
182 181
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
272 job->buf_frames = (avail < fill_amt) ? avail : fill_amt; 271 job->buf_frames = (avail < fill_amt) ? avail : fill_amt;
273 if (job->buf_frames > mix_buf_frames_) { 272 if (job->buf_frames > mix_buf_frames_) {
274 job->buf_frames = mix_buf_frames_; 273 job->buf_frames = mix_buf_frames_;
275 } 274 }
276 275
277 job->buf = mix_buf_.get(); 276 job->buf = mix_buf_.get();
278 job->start_pts_of = frames_sent_; 277 job->start_pts_of = frames_sent_;
279 job->local_to_output = &local_to_output_; 278 job->local_to_output = &local_to_output_;
280 job->local_to_output_gen = local_to_output_gen_; 279 job->local_to_output_gen = local_to_output_gen_;
281 280
282 // TODO(johngro): optimize this if we can. The first buffer we mix can just
283 // put its samples directly into the output buffer, and does not need to
284 // accumulate and clip. In theory, we only need to put silence in the
285 // places where our outputs are not going to already overwrite.
286 FillMixBufWithSilence(job->buf_frames);
287 return true; 281 return true;
288 } 282 }
289 283
290 // Wait until its time to mix some more data. 284 // Wait until its time to mix some more data.
291 SetNextSchedTime(low_buf_time); 285 SetNextSchedTime(low_buf_time);
292 return false; 286 return false;
293 } 287 }
294 288
295 bool AlsaOutput::FinishMixJob(const MixJob& job) { 289 bool AlsaOutput::FinishMixJob(const MixJob& job) {
296 DCHECK(job.buf == mix_buf_.get()); 290 DCHECK(job.buf == mix_buf_.get());
297 DCHECK(job.buf_frames); 291 DCHECK(job.buf_frames);
298 292
299 // We should always be able to write all of the data that we mixed. 293 // We should always be able to write all of the data that we mixed.
300 snd_pcm_sframes_t res; 294 snd_pcm_sframes_t res;
301 res = snd_pcm_writei(alsa_device_, job.buf, job.buf_frames); 295 res = snd_pcm_writei(alsa_device_, job.buf, job.buf_frames);
302 if (res != job.buf_frames) { 296 if (res != job.buf_frames) {
303 HandleAlsaError(res); 297 HandleAlsaError(res);
304 return false; 298 return false;
305 } 299 }
306 300
307 frames_sent_ += res; 301 frames_sent_ += res;
308 return true; 302 return true;
309 } 303 }
310 304
311 void AlsaOutput::FillMixBufWithSilence(uint32_t frames) {
312 DCHECK(mix_buf_);
313 DCHECK(frames <= mix_buf_frames_);
314
315 // TODO(johngro): someday, this may not be this simple. Filling unsigned
316 // multibyte sample formats, or floating point formats, will require something
317 // more sophisticated than filling with a single byte pattern.
318 ::memset(mix_buf_.get(), silence_byte_, frames * output_bytes_per_frame_);
319 }
320
321 void AlsaOutput::HandleAsUnderflow() { 305 void AlsaOutput::HandleAsUnderflow() {
322 snd_pcm_sframes_t res; 306 snd_pcm_sframes_t res;
323 307
324 // If we were already primed, then this is a legitimate underflow, not the 308 // If we were already primed, then this is a legitimate underflow, not the
325 // startup case or recovery from some other error. 309 // startup case or recovery from some other error.
326 if (primed_) { 310 if (primed_) {
327 // TODO(johngro): come up with a way to properly throttle this. Also, add a 311 // TODO(johngro): come up with a way to properly throttle this. Also, add a
328 // friendly name to the output so the log helps to identify which output 312 // friendly name to the output so the log helps to identify which output
329 // underflowed. 313 // underflowed.
330 LOG(WARNING) << "[" << this << "] : underflow"; 314 LOG(WARNING) << "[" << this << "] : underflow";
331 res = snd_pcm_recover(alsa_device_, -EPIPE, true); 315 res = snd_pcm_recover(alsa_device_, -EPIPE, true);
332 if (res < 0) { 316 if (res < 0) {
333 HandleAsError(res); 317 HandleAsError(res);
334 return; 318 return;
335 } 319 }
336 } 320 }
337 321
338 // TODO(johngro): We don't actually have to fill up the entire lead time with 322 // TODO(johngro): We don't actually have to fill up the entire lead time with
339 // silence. When we have better control of our thread priorities, prime this 323 // silence. When we have better control of our thread priorities, prime this
340 // with the minimimum amt we can get away with and still be able to start 324 // with the minimimum amt we can get away with and still be able to start
341 // mixing without underflowing. 325 // mixing without underflowing.
342 FillMixBufWithSilence(mix_buf_frames_); 326 output_formatter_->FillWithSilence(mix_buf_.get(), mix_buf_frames_);
343 res = snd_pcm_writei(alsa_device_, mix_buf_.get(), mix_buf_frames_); 327 res = snd_pcm_writei(alsa_device_, mix_buf_.get(), mix_buf_frames_);
344 328
345 if (res < 0) { 329 if (res < 0) {
346 HandleAsError(res); 330 HandleAsError(res);
347 return; 331 return;
348 } 332 }
349 333
350 primed_ = true; 334 primed_ = true;
351 local_to_output_known_ = false; 335 local_to_output_known_ = false;
352 SetNextSchedDelay(local_time::from_msec(1)); 336 SetNextSchedDelay(local_time::from_msec(1));
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
390 if (code == -EPIPE) { 374 if (code == -EPIPE) {
391 HandleAsUnderflow(); 375 HandleAsUnderflow();
392 } else { 376 } else {
393 HandleAsError(code); 377 HandleAsError(code);
394 } 378 }
395 } 379 }
396 380
397 } // namespace audio 381 } // namespace audio
398 } // namespace media 382 } // namespace media
399 } // namespace mojo 383 } // namespace mojo
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