OLD | NEW |
(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 AudioOutputPtr CreateDefaultAlsaOutput(AudioOutputManager* manager) { |
| 37 // TODO(johngro): Do better than this. If we really want to support |
| 38 // Linux/ALSA as a platform, we should be creating one output for each |
| 39 // physical output in the system, matching our configuration to the physical |
| 40 // output's configuration, and disabling resampling at the ALSA level. |
| 41 // |
| 42 // If we could own the output entirely and bypass the mixer to achieve lower |
| 43 // latency, that would be even better. |
| 44 AudioOutputPtr audio_out(audio::AlsaOutput::New(manager)); |
| 45 if (!audio_out) { return nullptr; } |
| 46 |
| 47 AlsaOutput* alsa_out = static_cast<AlsaOutput*>(audio_out.get()); |
| 48 DCHECK(alsa_out); |
| 49 |
| 50 LpcmMediaTypeDetailsPtr config(LpcmMediaTypeDetails::New()); |
| 51 config->frames_per_second = 48000; |
| 52 config->samples_per_frame = 2; |
| 53 config->sample_format = LpcmSampleFormat::SIGNED_16; |
| 54 |
| 55 if (alsa_out->Configure(config.Pass()) != MediaResult::OK) { |
| 56 return nullptr; |
| 57 } |
| 58 |
| 59 return audio_out; |
| 60 } |
| 61 |
| 62 AlsaOutput::AlsaOutput(AudioOutputManager* manager) |
| 63 : StandardOutputBase(manager) {} |
| 64 |
| 65 AlsaOutput::~AlsaOutput() { |
| 66 // We should have been cleaned up already, but in release builds, call cleanup |
| 67 // anyway, just in case something got missed. |
| 68 DCHECK(!alsa_device_); |
| 69 Cleanup(); |
| 70 } |
| 71 |
| 72 AudioOutputPtr AlsaOutput::New(AudioOutputManager* manager) { |
| 73 return AudioOutputPtr(new AlsaOutput(manager)); |
| 74 } |
| 75 |
| 76 MediaResult AlsaOutput::Configure(LpcmMediaTypeDetailsPtr config) { |
| 77 if (!config) { return MediaResult::INVALID_ARGUMENT; } |
| 78 if (output_format_) { return MediaResult::BAD_STATE; } |
| 79 |
| 80 uint32_t bytes_per_sample; |
| 81 switch (config->sample_format) { |
| 82 case LpcmSampleFormat::UNSIGNED_8: |
| 83 alsa_format_ = SND_PCM_FORMAT_U8; |
| 84 silence_byte_ = 0x80; |
| 85 bytes_per_sample = 1; |
| 86 break; |
| 87 |
| 88 case LpcmSampleFormat::SIGNED_16: |
| 89 alsa_format_ = SND_PCM_FORMAT_S16; |
| 90 silence_byte_ = 0x00; |
| 91 bytes_per_sample = 2; |
| 92 break; |
| 93 |
| 94 case LpcmSampleFormat::SIGNED_24_IN_32: |
| 95 default: |
| 96 return MediaResult::UNSUPPORTED_CONFIG; |
| 97 } |
| 98 |
| 99 if (SUPPORTED_SAMPLE_RATES.find(config->frames_per_second) == |
| 100 SUPPORTED_SAMPLE_RATES.end()) { |
| 101 return MediaResult::UNSUPPORTED_CONFIG; |
| 102 } |
| 103 |
| 104 if (SUPPORTED_CHANNEL_COUNTS.find(config->samples_per_frame) == |
| 105 SUPPORTED_CHANNEL_COUNTS.end()) { |
| 106 return MediaResult::UNSUPPORTED_CONFIG; |
| 107 } |
| 108 |
| 109 // Compute the ratio between frames and local time ticks. |
| 110 LinearTransform::Ratio sec_per_tick(LocalDuration::period::num, |
| 111 LocalDuration::period::den); |
| 112 LinearTransform::Ratio frames_per_sec(config->frames_per_second, 1); |
| 113 bool is_precise = LinearTransform::Ratio::Compose(frames_per_sec, |
| 114 sec_per_tick, |
| 115 &frames_per_tick_); |
| 116 DCHECK(is_precise); |
| 117 |
| 118 // Figure out how many bytes there are per frame. |
| 119 output_bytes_per_frame_ = bytes_per_sample * config->samples_per_frame; |
| 120 |
| 121 // Success |
| 122 output_format_ = config.Pass(); |
| 123 return MediaResult::OK; |
| 124 } |
| 125 |
| 126 MediaResult AlsaOutput::Init() { |
| 127 static const char* kAlsaDevice = "default"; |
| 128 |
| 129 if (!output_format_) { return MediaResult::BAD_STATE; } |
| 130 if (alsa_device_) { return MediaResult::BAD_STATE; } |
| 131 |
| 132 snd_pcm_sframes_t res; |
| 133 res = snd_pcm_open(&alsa_device_, |
| 134 kAlsaDevice, |
| 135 SND_PCM_STREAM_PLAYBACK, |
| 136 SND_PCM_NONBLOCK); |
| 137 if (res != 0) { |
| 138 LOG(ERROR) << "Failed to open ALSA device \"" << kAlsaDevice << "\"."; |
| 139 return MediaResult::INTERNAL_ERROR; |
| 140 } |
| 141 |
| 142 res = snd_pcm_set_params(alsa_device_, |
| 143 alsa_format_, |
| 144 SND_PCM_ACCESS_RW_INTERLEAVED, |
| 145 output_format_->samples_per_frame, |
| 146 output_format_->frames_per_second, |
| 147 0, // do not allow ALSA resample |
| 148 local_time::to_usec<unsigned int>(TARGET_LATENCY)); |
| 149 if (res) { |
| 150 LOG(ERROR) << "Failed to configure ALSA device \"" << kAlsaDevice << "\" " |
| 151 << "(res = " << res << ")"; |
| 152 LOG(ERROR) << "Requested samples per frame: " |
| 153 << output_format_->samples_per_frame; |
| 154 LOG(ERROR) << "Requested frames per second: " |
| 155 << output_format_->frames_per_second; |
| 156 LOG(ERROR) << "Requested ALSA format : " << alsa_format_; |
| 157 Cleanup(); |
| 158 return MediaResult::INTERNAL_ERROR; |
| 159 } |
| 160 |
| 161 // Figure out how big our mixing buffer needs to be, then allocate it. |
| 162 res = snd_pcm_avail_update(alsa_device_); |
| 163 if (res <= 0) { |
| 164 LOG(ERROR) << "[" << this << "] : " |
| 165 << "Fatal error (" << res |
| 166 << ") attempting to determine ALSA buffer size."; |
| 167 Cleanup(); |
| 168 return MediaResult::INTERNAL_ERROR; |
| 169 } |
| 170 |
| 171 mix_buf_frames_ = res; |
| 172 mix_buf_.reset(new uint8_t[mix_buf_frames_ * output_bytes_per_frame_]); |
| 173 |
| 174 return MediaResult::OK; |
| 175 } |
| 176 |
| 177 void AlsaOutput::Cleanup() { |
| 178 if (alsa_device_) { |
| 179 snd_pcm_close(alsa_device_); |
| 180 alsa_device_ = nullptr; |
| 181 } |
| 182 |
| 183 mix_buf_ = nullptr; |
| 184 mix_buf_frames_ = 0; |
| 185 } |
| 186 |
| 187 bool AlsaOutput::StartMixJob(MixJob* job, const LocalTime& process_start) { |
| 188 DCHECK(job); |
| 189 |
| 190 // Are we not primed? If so, fill a mix buffer with silence and send it to |
| 191 // the alsa device. Schedule a callback for a short time in the future so |
| 192 // ALSA has a chance to start the output and we can take our best guess of the |
| 193 // function which maps output frames to local time. |
| 194 if (!primed_) { |
| 195 HandleAsUnderflow(); |
| 196 return false; |
| 197 } |
| 198 |
| 199 // Figure out how many frames of audio we need to produce in order to top off |
| 200 // the buffer. If we are primed, but do not know the transformation between |
| 201 // audio frames and local time ticks, do our best to figure it out in the |
| 202 // process. |
| 203 snd_pcm_sframes_t avail; |
| 204 if (!local_to_output_known_) { |
| 205 snd_pcm_sframes_t delay; |
| 206 |
| 207 int res = snd_pcm_avail_delay(alsa_device_, &avail, &delay); |
| 208 LocalTime now = LocalClock::now(); |
| 209 |
| 210 if (res < 0) { |
| 211 HandleAlsaError(res); |
| 212 return false; |
| 213 } |
| 214 |
| 215 DCHECK_GE(delay, 0); |
| 216 int64_t now_ticks = now.time_since_epoch().count(); |
| 217 local_to_output_ = LinearTransform(now_ticks, frames_per_tick_, -delay); |
| 218 local_to_output_known_ = true; |
| 219 frames_sent_ = 0; |
| 220 while (++local_to_output_gen_ == MixJob::INVALID_GENERATION) {} |
| 221 } else { |
| 222 avail = snd_pcm_avail_update(alsa_device_); |
| 223 if (avail < 0) { |
| 224 HandleAlsaError(avail); |
| 225 return false; |
| 226 } |
| 227 } |
| 228 |
| 229 // Compute the time that we think we will completely underflow, then back off |
| 230 // from that by the low buffer threshold and use that to determine when we |
| 231 // should mix again. |
| 232 int64_t playout_time_ticks; |
| 233 bool trans_ok = local_to_output_.DoReverseTransform(frames_sent_, |
| 234 &playout_time_ticks); |
| 235 DCHECK(trans_ok); |
| 236 LocalTime playout_time = LocalTime(LocalDuration(playout_time_ticks)); |
| 237 LocalTime low_buf_time = playout_time - LOW_BUF_THRESH; |
| 238 |
| 239 if (process_start >= low_buf_time) { |
| 240 // Because of the way that ALSA consumes data and updates its internal |
| 241 // bookkeeping, it is possible that we are past our low buffer threshold, |
| 242 // but ALSA still thinks that there is no room to write new frames. If this |
| 243 // is the case, just try again a short amount of time in the future. |
| 244 DCHECK_GE(avail, 0); |
| 245 if (!avail) { |
| 246 SetNextSchedDelay(WAIT_FOR_ALSA_DELAY); |
| 247 return false; |
| 248 } |
| 249 |
| 250 // Limit the amt that we queue to be no more than what ALSA will currently |
| 251 // accept, or what it currently will take to fill us to our target latency. |
| 252 // |
| 253 // The playout target had better be ahead of the playout time, or we are |
| 254 // almost certainly going to underflow. If this happens, for whatever |
| 255 // reason, just try to send a full buffer and deal with the underflow when |
| 256 // ALSA notices it. |
| 257 int64_t fill_amt; |
| 258 LocalTime playout_target = LocalClock::now() + TARGET_LATENCY; |
| 259 if (playout_target > playout_time) { |
| 260 fill_amt = (playout_target - playout_time).count(); |
| 261 } else { |
| 262 fill_amt = TARGET_LATENCY.count(); |
| 263 } |
| 264 |
| 265 DCHECK_GE(fill_amt, 0); |
| 266 DCHECK_LE(fill_amt, std::numeric_limits<int32_t>::max()); |
| 267 fill_amt *= frames_per_tick_.numerator; |
| 268 fill_amt += frames_per_tick_.denominator - 1; |
| 269 fill_amt /= frames_per_tick_.denominator; |
| 270 |
| 271 job->buf_frames = (avail < fill_amt) ? avail : fill_amt; |
| 272 if (job->buf_frames > mix_buf_frames_) { |
| 273 job->buf_frames = mix_buf_frames_; |
| 274 } |
| 275 |
| 276 job->buf = mix_buf_.get(); |
| 277 job->start_pts_of = frames_sent_; |
| 278 job->local_to_output = &local_to_output_; |
| 279 job->local_to_output_gen = local_to_output_gen_; |
| 280 |
| 281 // TODO(johngro): optimize this if we can. The first buffer we mix can just |
| 282 // put its samples directly into the output buffer, and does not need to |
| 283 // accumulate and clip. In theory, we only need to put silence in the |
| 284 // places where our outputs are not going to already overwrite. |
| 285 FillMixBufWithSilence(job->buf_frames); |
| 286 return true; |
| 287 } |
| 288 |
| 289 // Wait until its time to mix some more data. |
| 290 SetNextSchedTime(low_buf_time); |
| 291 return false; |
| 292 } |
| 293 |
| 294 bool AlsaOutput::FinishMixJob(const MixJob& job) { |
| 295 DCHECK(job.buf == mix_buf_.get()); |
| 296 DCHECK(job.buf_frames); |
| 297 |
| 298 // We should always be able to write all of the data that we mixed. |
| 299 snd_pcm_sframes_t res; |
| 300 res = snd_pcm_writei(alsa_device_, job.buf, job.buf_frames); |
| 301 if (res != job.buf_frames) { |
| 302 HandleAlsaError(res); |
| 303 return false; |
| 304 } |
| 305 |
| 306 frames_sent_ += res; |
| 307 return true; |
| 308 } |
| 309 |
| 310 void AlsaOutput::FillMixBufWithSilence(uint32_t frames) { |
| 311 DCHECK(mix_buf_); |
| 312 DCHECK(frames <= mix_buf_frames_); |
| 313 |
| 314 // TODO(johngro): someday, this may not be this simple. Filling unsigned |
| 315 // multibyte sample formats, or floating point formats, will require something |
| 316 // more sophisticated than filling with a single byte pattern. |
| 317 ::memset(mix_buf_.get(), silence_byte_, frames * output_bytes_per_frame_); |
| 318 } |
| 319 |
| 320 void AlsaOutput::HandleAsUnderflow() { |
| 321 snd_pcm_sframes_t res; |
| 322 |
| 323 // If we were already primed, then this is a legitimate underflow, not the |
| 324 // startup case or recovery from some other error. |
| 325 if (primed_) { |
| 326 // TODO(johngro): come up with a way to properly throttle this. Also, add a |
| 327 // friendly name to the output so the log helps to identify which output |
| 328 // underflowed. |
| 329 LOG(WARNING) << "[" << this << "] : underflow"; |
| 330 res = snd_pcm_recover(alsa_device_, -EPIPE, true); |
| 331 if (res < 0) { |
| 332 HandleAsError(res); |
| 333 return; |
| 334 } |
| 335 } |
| 336 |
| 337 // TODO(johngro): We don't actually have to fill up the entire lead time with |
| 338 // silence. When we have better control of our thread priorities, prime this |
| 339 // with the minimimum amt we can get away with and still be able to start |
| 340 // mixing without underflowing. |
| 341 FillMixBufWithSilence(mix_buf_frames_); |
| 342 res = snd_pcm_writei(alsa_device_, mix_buf_.get(), mix_buf_frames_); |
| 343 |
| 344 if (res < 0) { |
| 345 HandleAsError(res); |
| 346 return; |
| 347 } |
| 348 |
| 349 primed_ = true; |
| 350 local_to_output_known_ = false; |
| 351 SetNextSchedDelay(local_time::from_msec(1)); |
| 352 } |
| 353 |
| 354 void AlsaOutput::HandleAsError(snd_pcm_sframes_t code) { |
| 355 // TODO(johngro): Throttle this somehow. |
| 356 LOG(WARNING) << "[" << this << "] : Attempting to recover from ALSA error " |
| 357 << code; |
| 358 |
| 359 if (IsRecoverableAlsaError(code)) { |
| 360 snd_pcm_sframes_t new_code; |
| 361 |
| 362 new_code = snd_pcm_recover(alsa_device_, code, true); |
| 363 DCHECK(!new_code || (new_code == code)); |
| 364 |
| 365 // If we recovered, or we didn't and the original error was EINTR, schedule |
| 366 // a retry time in the future and unwind. |
| 367 // |
| 368 // TODO(johngro): revisit the topic of errors we fail to snd_pcm_recover |
| 369 // from. If we cannot recover from them, we should probably close and |
| 370 // re-open the device. No matter what, we should put some form of limit on |
| 371 // how many times we try before really giving up and shutting down the |
| 372 // output for good. We also need to invent a good way to test these edge |
| 373 // cases. |
| 374 if (!new_code || (new_code == -EINTR)) { |
| 375 primed_ = false; |
| 376 local_to_output_known_ = false; |
| 377 SetNextSchedDelay(ERROR_RECOVERY_TIME); |
| 378 } |
| 379 } |
| 380 |
| 381 LOG(ERROR) << "[" << this << "] : Fatal ALSA error " |
| 382 << code << ". Shutting down"; |
| 383 ShutdownSelf(); |
| 384 } |
| 385 |
| 386 void AlsaOutput::HandleAlsaError(snd_pcm_sframes_t code) { |
| 387 // ALSA signals an underflow by returning -EPIPE from jobs. If the error code |
| 388 // is -EPIPE, treat this as an underflow and attempt to reprime the pipeline. |
| 389 if (code == -EPIPE) { |
| 390 HandleAsUnderflow(); |
| 391 } else { |
| 392 HandleAsError(code); |
| 393 } |
| 394 } |
| 395 |
| 396 } // namespace audio |
| 397 } // namespace media |
| 398 } // namespace mojo |
| 399 |
OLD | NEW |