| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 |
| OLD | NEW |