| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 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 // The audio stream implementation is made difficult because different methods | 5 // THREAD SAFETY |
| 6 // are available for calling depending on what state the stream is. Here is the | |
| 7 // state transition table for the stream. | |
| 8 // | 6 // |
| 9 // STATE_CREATED -> Open() -> STATE_OPENED | 7 // The AlsaPcmOutputStream object's internal state is accessed by two threads: |
| 10 // STATE_OPENED -> Start() -> STATE_STARTED | |
| 11 // STATE_OPENED -> Close() -> STATE_CLOSED | |
| 12 // STATE_STARTED -> Stop() -> STATE_STOPPED | |
| 13 // STATE_STARTED -> Close() -> STATE_CLOSING | STATE_CLOSED | |
| 14 // STATE_STOPPED -> Close() -> STATE_CLOSING | STATE_CLOSED | |
| 15 // STATE_CLOSING -> [automatic] -> STATE_CLOSED | |
| 16 // | 8 // |
| 17 // Error states and resource management: | 9 // client thread - creates the object and calls the public APIs. |
| 10 // message loop thread - executes all the internal tasks including querying |
| 11 // the data source for more data, writing to the alsa device, and closing |
| 12 // the alsa device. It does *not* handle opening the device though. |
| 18 // | 13 // |
| 19 // Entrance into STATE_STOPPED signals schedules a call to ReleaseResources(). | 14 // The class is designed so that most operations that read/modify the object's |
| 15 // internal state are done on the message loop thread. The exception is data |
| 16 // conatined in the |shared_data_| structure. Data in this structure needs to |
| 17 // be accessed by both threads, and should only be accessed when the |
| 18 // |shared_data_.lock_| is held. |
| 20 // | 19 // |
| 21 // Any state may transition to STATE_ERROR. On transitioning into STATE_ERROR, | 20 // All member variables that are not in |shared_data_| are created/destroyed on |
| 22 // the function doing the transition is reponsible for scheduling a call to | 21 // the |message_loop_|. This allows safe access to them from any task posted to |
| 23 // ReleaseResources() or otherwise ensuring resources are cleaned (eg., as is | 22 // |message_loop_|. The values in |shared_data_| are considered to be read-only |
| 24 // done in Open()). This should be done while holding the lock to avoid a | 23 // signals by tasks posted to |message_loop_| (see the SEMANTICS of |
| 25 // destruction race condition where the stream is deleted via ref-count before | 24 // |shared_data_| section below). Because of these two constraints, tasks can, |
| 26 // the ReleaseResources() task is scheduled. In particular, be careful of | 25 // and must, be coded to be safe in the face of a changing |shared_data_|. |
| 27 // resource management in a transtiion from STATE_STOPPED -> STATE_ERROR if | |
| 28 // that becomes necessary in the future. | |
| 29 // | 26 // |
| 30 // STATE_ERROR may transition to STATE_CLOSED. In this situation, no further | |
| 31 // resource management is done because it is assumed that the resource | |
| 32 // reclaimation was executed at the point of the state transition into | |
| 33 // STATE_ERROR. | |
| 34 // | 27 // |
| 35 // Entrance into STATE_CLOSED implies a transition through STATE_STOPPED, which | 28 // SEMANTICS OF |shared_data_| |
| 36 // triggers the resource management code. | |
| 37 // | 29 // |
| 38 // The destructor is not responsible for ultimate cleanup of resources. | 30 // Though |shared_data_| is accessable by both threads, the code has been |
| 39 // Instead, it only checks that the stream is in a state where all resources | 31 // structured so that all mutations to |shared_data_| are only done in the |
| 40 // have been cleaned up. These states are STATE_CREATED, STATE_CLOSED, | 32 // client thread. The message loop thread only ever reads the shared data. |
| 41 // STATE_ERROR. | |
| 42 // | 33 // |
| 43 // TODO(ajwong): This incorrectly handles the period size for filling of the | 34 // This reduces the need for critical sections because the public API code can |
| 44 // ALSA device buffer. Currently the period size is hardcoded, and not | 35 // assume that no mutations occur to the |shared_data_| between queries. |
| 45 // reported to the sound device. Also, there are options that let the device | 36 // |
| 46 // wait until the buffer is minimally filled before playing. Those should be | 37 // On the message loop side, most tasks have been coded such that they can |
| 47 // explored. Also consider doing the device interactions either outside of the | 38 // operate safely regardless of when state changes happen to |shared_data_|. |
| 48 // class lock, or under a different lock to avoid unecessarily blocking other | 39 // Code that is sensitive to the timing holds the |shared_data_.lock_| |
| 49 // threads. | 40 // explicitly for the duration of the critical section. |
| 41 // |
| 42 // |
| 43 // SEMANTICS OF CloseTask() |
| 44 // |
| 45 // The CloseTask() is responsible for cleaning up any resources that were |
| 46 // acquired after a successful Open(). After a CloseTask() has executed, |
| 47 // scheduling of reads should stop. Currently scheduled tasks may run, but |
| 48 // they should not attempt to access any of the internal structures beyond |
| 49 // querying the |stop_stream_| flag and no-oping themselves. This will |
| 50 // guarantee that eventually no more tasks will be posted into the message |
| 51 // loop, and the AlsaPcmOutputStream will be able to delete itself. |
| 52 // |
| 53 // |
| 54 // SEMANTICS OF ERROR STATES |
| 55 // |
| 56 // The object has two distinct error states: |shared_data_.state_| == kInError |
| 57 // and |stop_stream_|. The |shared_data_.state_| state is only settable |
| 58 // by the client thread, and thus cannot be used to signal when the ALSA device |
| 59 // fails due to a hardware (or other low-level) event. The |stop_stream_| |
| 60 // variable is only accessed by the message loop thread; it is used to indicate |
| 61 // that the playback_handle should no longer be used either because of a |
| 62 // hardware/low-level event, or because the CloseTask() has been run. |
| 63 // |
| 64 // When |shared_data_.state_| == kInError, all public API functions will fail |
| 65 // with an error (Start() will call the OnError() function on the callback |
| 66 // immediately), or no-op themselves with the exception of Close(). Even if an |
| 67 // error state has been entered, if Open() has previously returned successfully, |
| 68 // Close() must be called to cleanup the ALSA devices and release resources. |
| 69 // |
| 70 // When |stop_stream_| is set, no more commands will be made against the |
| 71 // ALSA device, and playback will effectively stop. From the client's point of |
| 72 // view, it will seem that the device has just clogged and stopped requesting |
| 73 // data. |
| 50 | 74 |
| 51 #include "media/audio/linux/alsa_output.h" | 75 #include "media/audio/linux/alsa_output.h" |
| 52 | 76 |
| 53 #include <algorithm> | 77 #include <algorithm> |
| 54 | 78 |
| 55 #include "base/logging.h" | 79 #include "base/logging.h" |
| 56 #include "base/stl_util-inl.h" | 80 #include "base/stl_util-inl.h" |
| 57 #include "base/time.h" | 81 #include "base/time.h" |
| 58 #include "media/audio/audio_util.h" | 82 #include "media/audio/audio_util.h" |
| 59 | 83 #include "media/audio/linux/alsa_wrapper.h" |
| 60 // Require 10ms latency from the audio device. Taken from ALSA documentation | 84 |
| 61 // example. | 85 // Amount of time to wait if we've exhausted the data source. This is to avoid |
| 62 // TODO(ajwong): Figure out what this parameter actually does, and what a good | 86 // busy looping. |
| 63 // value would be. | 87 static const int kNoDataSleepMilliseconds = 10; |
| 64 static const unsigned int kTargetLatencyMicroseconds = 10000; | 88 |
| 65 | 89 // Set to 0 during debugging if you want error messages due to underrun |
| 66 // Minimal amount of time to sleep. If any future event is expected to | 90 // events or other recoverable errors. |
| 67 // execute within this timeframe, treat it as if it should execute immediately. | 91 #if defined(NDEBUG) |
| 68 // | 92 static const int kPcmRecoverIsSilent = 1; |
| 69 // TODO(ajwong): Determine if there is a sensible minimum sleep resolution and | 93 #else |
| 70 // adjust accordingly. | 94 static const int kPcmRecoverIsSilent = 0; |
| 71 static const int64 kMinSleepMilliseconds = 10L; | 95 #endif |
| 72 | 96 |
| 73 const char* AlsaPCMOutputStream::kDefaultDevice = "default"; | 97 const char AlsaPcmOutputStream::kDefaultDevice[] = "default"; |
| 74 | 98 |
| 75 AlsaPCMOutputStream::AlsaPCMOutputStream(const std::string& device_name, | 99 namespace { |
| 76 int min_buffer_ms, | 100 |
| 101 snd_pcm_format_t BitsToFormat(char bits_per_sample) { |
| 102 switch (bits_per_sample) { |
| 103 case 8: |
| 104 return SND_PCM_FORMAT_S8; |
| 105 |
| 106 case 16: |
| 107 return SND_PCM_FORMAT_S16; |
| 108 |
| 109 case 24: |
| 110 return SND_PCM_FORMAT_S24; |
| 111 |
| 112 case 32: |
| 113 return SND_PCM_FORMAT_S32; |
| 114 |
| 115 default: |
| 116 return SND_PCM_FORMAT_UNKNOWN; |
| 117 } |
| 118 } |
| 119 |
| 120 } // namespace |
| 121 |
| 122 std::ostream& operator<<(std::ostream& os, |
| 123 AlsaPcmOutputStream::InternalState state) { |
| 124 switch (state) { |
| 125 case AlsaPcmOutputStream::kInError: |
| 126 os << "kInError"; |
| 127 break; |
| 128 case AlsaPcmOutputStream::kCreated: |
| 129 os << "kCreated"; |
| 130 break; |
| 131 case AlsaPcmOutputStream::kIsOpened: |
| 132 os << "kIsOpened"; |
| 133 break; |
| 134 case AlsaPcmOutputStream::kIsPlaying: |
| 135 os << "kIsPlaying"; |
| 136 break; |
| 137 case AlsaPcmOutputStream::kIsStopped: |
| 138 os << "kIsStopped"; |
| 139 break; |
| 140 case AlsaPcmOutputStream::kIsClosed: |
| 141 os << "kIsClosed"; |
| 142 break; |
| 143 }; |
| 144 return os; |
| 145 } |
| 146 |
| 147 AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name, |
| 77 AudioManager::Format format, | 148 AudioManager::Format format, |
| 78 int channels, | 149 int channels, |
| 79 int sample_rate, | 150 int sample_rate, |
| 80 char bits_per_sample) | 151 int bits_per_sample, |
| 81 : state_(STATE_CREATED), | 152 AlsaWrapper* wrapper, |
| 153 MessageLoop* message_loop) |
| 154 : shared_data_(MessageLoop::current()), |
| 82 device_name_(device_name), | 155 device_name_(device_name), |
| 156 pcm_format_(BitsToFormat(bits_per_sample)), |
| 157 channels_(channels), |
| 158 sample_rate_(sample_rate), |
| 159 bytes_per_sample_(bits_per_sample / 8), |
| 160 bytes_per_frame_(channels_ * bits_per_sample / 8), |
| 161 stop_stream_(false), |
| 162 wrapper_(wrapper), |
| 83 playback_handle_(NULL), | 163 playback_handle_(NULL), |
| 84 source_callback_(NULL), | 164 source_callback_(NULL), |
| 85 playback_thread_("PlaybackThread"), | 165 frames_per_packet_(0), |
| 86 channels_(channels), | 166 client_thread_loop_(MessageLoop::current()), |
| 87 sample_rate_(sample_rate), | 167 message_loop_(message_loop) { |
| 88 bits_per_sample_(bits_per_sample), | |
| 89 min_buffer_frames_((min_buffer_ms * sample_rate_) / | |
| 90 base::Time::kMillisecondsPerSecond), | |
| 91 packet_size_(0), | |
| 92 device_write_suspended_(true), // Start suspended. | |
| 93 resources_released_(false), | |
| 94 volume_(1.0) { | |
| 95 // Reference self to avoid accidental deletion before the message loop is | |
| 96 // done. | |
| 97 AddRef(); | |
| 98 | 168 |
| 99 // Sanity check input values. | 169 // Sanity check input values. |
| 170 // TODO(ajwong): Just try what happens if we allow non 2-channel audio. |
| 100 if (channels_ != 2) { | 171 if (channels_ != 2) { |
| 101 LOG(WARNING) << "Only 2-channel audio is supported right now."; | 172 LOG(WARNING) << "Only 2-channel audio is supported right now."; |
| 102 state_ = STATE_ERROR; | 173 shared_data_.TransitionTo(kInError); |
| 103 } | 174 } |
| 104 | 175 |
| 105 if (AudioManager::AUDIO_PCM_LINEAR != format) { | 176 if (AudioManager::AUDIO_PCM_LINEAR != format) { |
| 106 LOG(WARNING) << "Only linear PCM supported."; | 177 LOG(WARNING) << "Only linear PCM supported."; |
| 107 state_ = STATE_ERROR; | 178 shared_data_.TransitionTo(kInError); |
| 108 } | 179 } |
| 109 | 180 |
| 110 if (bits_per_sample % 8 != 0) { | 181 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { |
| 111 // We do this explicitly just incase someone messes up the switch below. | 182 LOG(WARNING) << "Unsupported bits per sample: " << bits_per_sample; |
| 112 LOG(WARNING) << "Only allow byte-aligned samples"; | 183 shared_data_.TransitionTo(kInError); |
| 113 state_ = STATE_ERROR; | 184 } |
| 114 } | 185 } |
| 115 | 186 |
| 116 switch (bits_per_sample) { | 187 AlsaPcmOutputStream::~AlsaPcmOutputStream() { |
| 117 case 8: | 188 InternalState state = shared_data_.state(); |
| 118 pcm_format_ = SND_PCM_FORMAT_S8; | 189 DCHECK(state == kCreated || state == kIsClosed || state == kInError); |
| 119 break; | 190 |
| 120 | 191 // TODO(ajwong): Ensure that CloseTask has been called and the |
| 121 case 16: | 192 // playback handle released by DCHECKing that playback_handle_ is NULL. |
| 122 pcm_format_ = SND_PCM_FORMAT_S16; | 193 // Currently, because of Bug 18217, there is a race condition on destruction |
| 123 break; | 194 // where the stream is not always stopped and closed, causing this to fail. |
| 124 | 195 } |
| 125 case 24: | 196 |
| 126 pcm_format_ = SND_PCM_FORMAT_S24; | 197 bool AlsaPcmOutputStream::Open(size_t packet_size) { |
| 127 break; | 198 DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| 128 | 199 |
| 129 case 32: | 200 DCHECK_EQ(0U, packet_size % bytes_per_frame_) |
| 130 pcm_format_ = SND_PCM_FORMAT_S32; | 201 << "Buffers should end on a frame boundary. Frame size: " |
| 131 break; | 202 << bytes_per_frame_; |
| 132 | 203 |
| 133 default: | 204 if (!shared_data_.CanTransitionTo(kIsOpened)) { |
| 134 LOG(DFATAL) << "Unsupported bits per sample: " << bits_per_sample_; | 205 NOTREACHED() << "Invalid state: " << shared_data_.state(); |
| 135 state_ = STATE_ERROR; | |
| 136 break; | |
| 137 } | |
| 138 | |
| 139 // Interleaved audio is expected, so each frame has one sample per channel. | |
| 140 bytes_per_frame_ = channels_ * (bits_per_sample_ / 8); | |
| 141 } | |
| 142 | |
| 143 AlsaPCMOutputStream::~AlsaPCMOutputStream() { | |
| 144 AutoLock l(lock_); | |
| 145 // In STATE_CREATED, STATE_CLOSED, and STATE_ERROR, resources are guaranteed | |
| 146 // to be released. | |
| 147 CHECK(state_ == STATE_CREATED || | |
| 148 state_ == STATE_CLOSED || | |
| 149 state_ == STATE_ERROR); | |
| 150 } | |
| 151 | |
| 152 bool AlsaPCMOutputStream::Open(size_t packet_size) { | |
| 153 AutoLock l(lock_); | |
| 154 | |
| 155 // Check that stream is coming from the correct state and early out if not. | |
| 156 if (state_ == STATE_ERROR) { | |
| 157 return false; | 206 return false; |
| 158 } | 207 } |
| 159 if (state_ != STATE_CREATED) { | 208 |
| 160 NOTREACHED() << "Stream must be in STATE_CREATED on Open. Instead in: " | 209 // Try to open the device. |
| 161 << state_; | 210 snd_pcm_t* handle = NULL; |
| 162 return false; | 211 int error = wrapper_->PcmOpen(&handle, device_name_.c_str(), |
| 163 } | 212 SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); |
| 164 | |
| 165 // Open the device and set the parameters. | |
| 166 // TODO(ajwong): Can device open block? Probably. If yes, we need to move | |
| 167 // the open call into a different thread. | |
| 168 int error = snd_pcm_open(&playback_handle_, device_name_.c_str(), | |
| 169 SND_PCM_STREAM_PLAYBACK, 0); | |
| 170 if (error < 0) { | 213 if (error < 0) { |
| 171 LOG(ERROR) << "Cannot open audio device (" << device_name_ << "): " | 214 LOG(ERROR) << "Cannot open audio device (" << device_name_ << "): " |
| 172 << snd_strerror(error); | 215 << wrapper_->StrError(error); |
| 173 EnterStateError_Locked(); | |
| 174 return false; | 216 return false; |
| 175 } | 217 } |
| 176 if ((error = snd_pcm_set_params(playback_handle_, | 218 |
| 177 pcm_format_, | 219 // Configure the device for software resampling, and add enough buffer for |
| 178 SND_PCM_ACCESS_RW_INTERLEAVED, | 220 // two audio packets. |
| 179 channels_, | 221 int micros_per_packet = |
| 180 sample_rate_, | 222 FramesToMicros(packet_size / bytes_per_frame_, sample_rate_); |
| 181 1, // soft_resample -- let ALSA resample | 223 if ((error = wrapper_->PcmSetParams(handle, |
| 182 kTargetLatencyMicroseconds)) < 0) { | 224 pcm_format_, |
| 183 LOG(ERROR) << "Unable to set PCM parameters: " << snd_strerror(error); | 225 SND_PCM_ACCESS_RW_INTERLEAVED, |
| 184 if (!CloseDevice_Locked()) { | 226 channels_, |
| 227 sample_rate_, |
| 228 1, // soft_resample -- let ALSA resample |
| 229 micros_per_packet * 2)) < 0) { |
| 230 LOG(ERROR) << "Unable to set PCM parameters for (" << device_name_ |
| 231 << "): " << wrapper_->StrError(error); |
| 232 if (!CloseDevice(handle)) { |
| 233 // TODO(ajwong): Retry on certain errors? |
| 185 LOG(WARNING) << "Unable to close audio device. Leaking handle."; | 234 LOG(WARNING) << "Unable to close audio device. Leaking handle."; |
| 186 } | 235 } |
| 187 playback_handle_ = NULL; | |
| 188 EnterStateError_Locked(); | |
| 189 return false; | 236 return false; |
| 190 } | 237 } |
| 191 | 238 |
| 192 // Configure the buffering. | 239 // We do not need to check if the transition was successful because |
| 193 packet_size_ = packet_size; | 240 // CanTransitionTo() was checked above, and it is assumed that this |
| 194 DCHECK_EQ(0U, packet_size_ % bytes_per_frame_) | 241 // object's public API is only called on one thread so the state cannot |
| 195 << "Buffers should end on a frame boundary. Frame size: " | 242 // transition out from under us. |
| 196 << bytes_per_frame_; | 243 shared_data_.TransitionTo(kIsOpened); |
| 197 | 244 message_loop_->PostTask( |
| 198 // Everything is okay. Stream is officially STATE_OPENED for business. | 245 FROM_HERE, |
| 199 state_ = STATE_OPENED; | 246 NewRunnableMethod(this, &AlsaPcmOutputStream::FinishOpen, |
| 247 handle, packet_size)); |
| 200 | 248 |
| 201 return true; | 249 return true; |
| 202 } | 250 } |
| 203 | 251 |
| 204 void AlsaPCMOutputStream::Start(AudioSourceCallback* callback) { | 252 void AlsaPcmOutputStream::Close() { |
| 253 DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| 254 |
| 255 if (shared_data_.TransitionTo(kIsClosed) == kIsClosed) { |
| 256 message_loop_->PostTask( |
| 257 FROM_HERE, |
| 258 NewRunnableMethod(this, &AlsaPcmOutputStream::CloseTask)); |
| 259 } |
| 260 } |
| 261 |
| 262 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { |
| 263 DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| 264 |
| 265 CHECK(callback); |
| 266 |
| 267 // Only post the task if we can enter the playing state. |
| 268 if (shared_data_.TransitionTo(kIsPlaying) == kIsPlaying) { |
| 269 message_loop_->PostTask( |
| 270 FROM_HERE, |
| 271 NewRunnableMethod(this, &AlsaPcmOutputStream::StartTask, callback)); |
| 272 } |
| 273 } |
| 274 |
| 275 void AlsaPcmOutputStream::Stop() { |
| 276 DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| 277 |
| 278 shared_data_.TransitionTo(kIsStopped); |
| 279 } |
| 280 |
| 281 void AlsaPcmOutputStream::SetVolume(double left_level, double right_level) { |
| 282 DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| 283 |
| 284 shared_data_.set_volume(static_cast<float>(left_level)); |
| 285 } |
| 286 |
| 287 void AlsaPcmOutputStream::GetVolume(double* left_level, double* right_level) { |
| 288 DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| 289 |
| 290 *left_level = *right_level = shared_data_.volume(); |
| 291 } |
| 292 |
| 293 void AlsaPcmOutputStream::FinishOpen(snd_pcm_t* playback_handle, |
| 294 size_t packet_size) { |
| 295 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 296 |
| 297 playback_handle_ = playback_handle; |
| 298 packet_.reset(new Packet(packet_size)); |
| 299 frames_per_packet_ = packet_size / bytes_per_frame_; |
| 300 } |
| 301 |
| 302 void AlsaPcmOutputStream::StartTask(AudioSourceCallback* callback) { |
| 303 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 304 |
| 305 source_callback_ = callback; |
| 306 |
| 307 // When starting again, drop all packets in the device and prepare it again |
| 308 // incase we are restarting from a pause state and need to flush old data. |
| 309 int error = wrapper_->PcmDrop(playback_handle_); |
| 310 if (error < 0 && error != -EAGAIN) { |
| 311 LOG(ERROR) << "Failure clearing playback device (" |
| 312 << wrapper_->PcmName(playback_handle_) << "): " |
| 313 << wrapper_->StrError(error); |
| 314 stop_stream_ = true; |
| 315 return; |
| 316 } |
| 317 |
| 318 error = wrapper_->PcmPrepare(playback_handle_); |
| 319 if (error < 0 && error != -EAGAIN) { |
| 320 LOG(ERROR) << "Failure preparing stream (" |
| 321 << wrapper_->PcmName(playback_handle_) << "): " |
| 322 << wrapper_->StrError(error); |
| 323 stop_stream_ = true; |
| 324 return; |
| 325 } |
| 326 |
| 327 // Do a best-effort write of 2 packets to pre-roll. |
| 328 // |
| 329 // TODO(ajwong): Make this track with the us_latency set in Open(). |
| 330 // Also handle EAGAIN. |
| 331 BufferPacket(packet_.get()); |
| 332 WritePacket(packet_.get()); |
| 333 BufferPacket(packet_.get()); |
| 334 WritePacket(packet_.get()); |
| 335 |
| 336 ScheduleNextWrite(packet_.get()); |
| 337 } |
| 338 |
| 339 void AlsaPcmOutputStream::CloseTask() { |
| 340 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 341 |
| 342 // Shutdown the audio device. |
| 343 if (playback_handle_ && !CloseDevice(playback_handle_)) { |
| 344 LOG(WARNING) << "Unable to close audio device. Leaking handle."; |
| 345 } |
| 346 playback_handle_ = NULL; |
| 347 |
| 348 // Release the buffer. |
| 349 packet_.reset(); |
| 350 |
| 351 // The |source_callback_| may be NULL if the stream is being closed before it |
| 352 // was ever started. |
| 353 if (source_callback_) { |
| 354 // TODO(ajwong): We need to call source_callback_->OnClose(), but the |
| 355 // ownerships of the callback is broken right now, so we'd crash. Instead, |
| 356 // just leak. Bug 18217. |
| 357 source_callback_ = NULL; |
| 358 } |
| 359 |
| 360 // Signal anything that might already be scheduled to stop. |
| 361 stop_stream_ = true; |
| 362 } |
| 363 |
| 364 void AlsaPcmOutputStream::BufferPacket(Packet* packet) { |
| 365 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 366 |
| 367 // If stopped, simulate a 0-lengthed packet. |
| 368 if (stop_stream_) { |
| 369 packet->used = packet->size = 0; |
| 370 return; |
| 371 } |
| 372 |
| 373 // Request more data if we don't have any cached. |
| 374 if (packet->used >= packet->size) { |
| 375 packet->used = 0; |
| 376 packet->size = source_callback_->OnMoreData(this, packet->buffer.get(), |
| 377 packet->capacity); |
| 378 CHECK(packet->size <= packet->capacity) << "Data source overran buffer."; |
| 379 |
| 380 // This should not happen, but incase it does, drop any trailing bytes |
| 381 // that aren't large enough to make a frame. Without this, packet writing |
| 382 // may stall because the last few bytes in the packet may never get used by |
| 383 // WritePacket. |
| 384 DCHECK(packet->size % bytes_per_frame_ == 0); |
| 385 packet->size = (packet->size / bytes_per_frame_) * bytes_per_frame_; |
| 386 |
| 387 media::AdjustVolume(packet->buffer.get(), |
| 388 packet->size, |
| 389 channels_, |
| 390 bytes_per_sample_, |
| 391 shared_data_.volume()); |
| 392 } |
| 393 } |
| 394 |
| 395 void AlsaPcmOutputStream::WritePacket(Packet* packet) { |
| 396 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 397 |
| 398 CHECK(packet->size % bytes_per_frame_ == 0); |
| 399 |
| 400 // If the device is in error, just eat the bytes. |
| 401 if (stop_stream_) { |
| 402 packet->used = packet->size; |
| 403 return; |
| 404 } |
| 405 |
| 406 if (packet->used < packet->size) { |
| 407 char* buffer_pos = packet->buffer.get() + packet->used; |
| 408 snd_pcm_sframes_t frames = FramesInPacket(*packet, bytes_per_frame_); |
| 409 |
| 410 DCHECK_GT(frames, 0); |
| 411 |
| 412 snd_pcm_sframes_t frames_written = |
| 413 wrapper_->PcmWritei(playback_handle_, buffer_pos, frames); |
| 414 if (frames_written < 0) { |
| 415 // Attempt once to immediately recover from EINTR, |
| 416 // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket |
| 417 // will eventually be called again, so eventual recovery will happen if |
| 418 // muliple retries are required. |
| 419 frames_written = wrapper_->PcmRecover(playback_handle_, |
| 420 frames_written, |
| 421 kPcmRecoverIsSilent); |
| 422 } |
| 423 |
| 424 if (frames_written < 0) { |
| 425 // TODO(ajwong): Is EAGAIN the only error we want to except from stopping |
| 426 // the pcm playback? |
| 427 if (frames_written != -EAGAIN) { |
| 428 LOG(ERROR) << "Failed to write to pcm device: " |
| 429 << wrapper_->StrError(frames_written); |
| 430 // TODO(ajwong): We need to call source_callback_->OnError(), but the |
| 431 // ownerships of the callback is broken right now, so we'd crash. |
| 432 // Instead, just leak. Bug 18217. |
| 433 stop_stream_ = true; |
| 434 } |
| 435 } else { |
| 436 packet->used += frames_written * bytes_per_frame_; |
| 437 } |
| 438 } |
| 439 } |
| 440 |
| 441 void AlsaPcmOutputStream::WriteTask() { |
| 442 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 443 |
| 444 if (stop_stream_) { |
| 445 return; |
| 446 } |
| 447 |
| 448 BufferPacket(packet_.get()); |
| 449 WritePacket(packet_.get()); |
| 450 |
| 451 ScheduleNextWrite(packet_.get()); |
| 452 } |
| 453 |
| 454 void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { |
| 455 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 456 |
| 457 if (stop_stream_) { |
| 458 return; |
| 459 } |
| 460 |
| 461 // Calculate when we should have enough buffer for another packet of data. |
| 462 int frames_leftover = FramesInPacket(*current_packet, bytes_per_frame_); |
| 463 int frames_needed = |
| 464 frames_leftover > 0 ? frames_leftover : frames_per_packet_; |
| 465 int frames_until_empty_enough = frames_needed - GetAvailableFrames(); |
| 466 int next_fill_time_ms = |
| 467 FramesToMillis(frames_until_empty_enough, sample_rate_); |
| 468 |
| 469 // Avoid busy looping if the data source is exhausted. |
| 470 if (current_packet->size == 0) { |
| 471 next_fill_time_ms = std::max(next_fill_time_ms, kNoDataSleepMilliseconds); |
| 472 } |
| 473 |
| 474 // Only schedule more reads/writes if we are still in the playing state. |
| 475 if (shared_data_.state() == kIsPlaying) { |
| 476 if (next_fill_time_ms <= 0) { |
| 477 message_loop_->PostTask( |
| 478 FROM_HERE, |
| 479 NewRunnableMethod(this, &AlsaPcmOutputStream::WriteTask)); |
| 480 } else { |
| 481 // TODO(ajwong): Measure the reliability of the delay interval. Use |
| 482 // base/histogram.h. |
| 483 message_loop_->PostDelayedTask( |
| 484 FROM_HERE, |
| 485 NewRunnableMethod(this, &AlsaPcmOutputStream::WriteTask), |
| 486 next_fill_time_ms); |
| 487 } |
| 488 } |
| 489 } |
| 490 |
| 491 snd_pcm_sframes_t AlsaPcmOutputStream::FramesInPacket(const Packet& packet, |
| 492 int bytes_per_frame) { |
| 493 return (packet.size - packet.used) / bytes_per_frame; |
| 494 } |
| 495 |
| 496 int64 AlsaPcmOutputStream::FramesToMicros(int frames, int sample_rate) { |
| 497 return frames * base::Time::kMicrosecondsPerSecond / sample_rate; |
| 498 } |
| 499 |
| 500 int64 AlsaPcmOutputStream::FramesToMillis(int frames, int sample_rate) { |
| 501 return frames * base::Time::kMillisecondsPerSecond / sample_rate; |
| 502 } |
| 503 |
| 504 bool AlsaPcmOutputStream::CloseDevice(snd_pcm_t* handle) { |
| 505 int error = wrapper_->PcmClose(handle); |
| 506 if (error < 0) { |
| 507 LOG(ERROR) << "Cannot close audio device (" << wrapper_->PcmName(handle) |
| 508 << "): " << wrapper_->StrError(error); |
| 509 return false; |
| 510 } |
| 511 |
| 512 return true; |
| 513 } |
| 514 |
| 515 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { |
| 516 DCHECK_EQ(MessageLoop::current(), message_loop_); |
| 517 |
| 518 if (stop_stream_) { |
| 519 return 0; |
| 520 } |
| 521 |
| 522 // Find the number of frames queued in the sound device. |
| 523 snd_pcm_sframes_t available_frames = |
| 524 wrapper_->PcmAvailUpdate(playback_handle_); |
| 525 if (available_frames < 0) { |
| 526 available_frames = wrapper_->PcmRecover(playback_handle_, |
| 527 available_frames, |
| 528 kPcmRecoverIsSilent); |
| 529 } |
| 530 if (available_frames < 0) { |
| 531 LOG(ERROR) << "Failed querying available frames. Assuming 0: " |
| 532 << wrapper_->StrError(available_frames); |
| 533 return 0; |
| 534 } |
| 535 |
| 536 return available_frames; |
| 537 } |
| 538 |
| 539 AlsaPcmOutputStream::SharedData::SharedData( |
| 540 MessageLoop* state_transition_loop) |
| 541 : state_(kCreated), |
| 542 volume_(1.0f), |
| 543 state_transition_loop_(state_transition_loop) { |
| 544 } |
| 545 |
| 546 bool AlsaPcmOutputStream::SharedData::CanTransitionTo(InternalState to) { |
| 205 AutoLock l(lock_); | 547 AutoLock l(lock_); |
| 206 | 548 return CanTransitionTo_Locked(to); |
| 207 // Check that stream is coming from the correct state and early out if not. | 549 } |
| 208 if (state_ == STATE_ERROR) { | 550 |
| 209 return; | 551 bool AlsaPcmOutputStream::SharedData::CanTransitionTo_Locked( |
| 210 } | 552 InternalState to) { |
| 211 if (state_ != STATE_OPENED) { | 553 lock_.AssertAcquired(); |
| 212 NOTREACHED() << "Can only be started from STATE_OPEN. Current state: " | 554 |
| 213 << state_; | 555 switch (state_) { |
| 214 return; | 556 case kCreated: |
| 215 } | 557 return to == kIsOpened || to == kIsClosed || to == kInError; |
| 216 | 558 |
| 217 source_callback_ = callback; | 559 case kIsOpened: |
| 218 | 560 return to == kIsPlaying || to == kIsStopped || |
| 219 playback_thread_.Start(); | 561 to == kIsClosed || to == kInError; |
| 220 playback_thread_.message_loop()->PostTask(FROM_HERE, | 562 |
| 221 NewRunnableMethod(this, &AlsaPCMOutputStream::BufferPackets)); | 563 case kIsPlaying: |
| 222 | 564 return to == kIsStopped || to == kIsClosed || to == kInError; |
| 223 state_ = STATE_STARTED; | 565 |
| 224 } | 566 case kIsStopped: |
| 225 | 567 return to == kIsPlaying || to == kIsStopped || |
| 226 void AlsaPCMOutputStream::Stop() { | 568 to == kIsClosed || to == kInError; |
| 569 |
| 570 case kInError: |
| 571 return to == kIsClosed || to == kInError; |
| 572 |
| 573 case kIsClosed: |
| 574 default: |
| 575 return false; |
| 576 } |
| 577 } |
| 578 |
| 579 AlsaPcmOutputStream::InternalState |
| 580 AlsaPcmOutputStream::SharedData::TransitionTo(InternalState to) { |
| 581 DCHECK_EQ(MessageLoop::current(), state_transition_loop_); |
| 582 |
| 227 AutoLock l(lock_); | 583 AutoLock l(lock_); |
| 228 // If the stream is in STATE_ERROR, it is effectively stopped already. | 584 if (!CanTransitionTo_Locked(to)) { |
| 229 if (state_ == STATE_ERROR) { | 585 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; |
| 230 return; | 586 state_ = kInError; |
| 231 } | 587 } else { |
| 232 StopInternal_Locked(); | 588 state_ = to; |
| 233 } | 589 } |
| 234 | 590 return state_; |
| 235 void AlsaPCMOutputStream::StopInternal_Locked() { | 591 } |
| 236 // Check the lock is held in a debug build. | 592 |
| 237 DCHECK((lock_.AssertAcquired(), true)); | 593 AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::SharedData::state() { |
| 238 | |
| 239 if (state_ != STATE_STARTED) { | |
| 240 NOTREACHED() << "Stream must be in STATE_STARTED to Stop. Instead in: " | |
| 241 << state_; | |
| 242 return; | |
| 243 } | |
| 244 | |
| 245 // Move immediately to STATE_STOPPED to signal that all functions should cease | |
| 246 // working at this point. Then post a task to the playback thread to release | |
| 247 // resources. | |
| 248 state_ = STATE_STOPPED; | |
| 249 | |
| 250 playback_thread_.message_loop()->PostTask( | |
| 251 FROM_HERE, | |
| 252 NewRunnableMethod(this, &AlsaPCMOutputStream::ReleaseResources)); | |
| 253 } | |
| 254 | |
| 255 void AlsaPCMOutputStream::EnterStateError_Locked() { | |
| 256 // Check the lock is held in a debug build. | |
| 257 DCHECK((lock_.AssertAcquired(), true)); | |
| 258 | |
| 259 state_ = STATE_ERROR; | |
| 260 resources_released_ = true; | |
| 261 | |
| 262 // TODO(ajwong): Call OnError() on source_callback_. | |
| 263 } | |
| 264 | |
| 265 void AlsaPCMOutputStream::Close() { | |
| 266 AutoLock l(lock_); | 594 AutoLock l(lock_); |
| 267 | 595 return state_; |
| 268 // If in STATE_ERROR, all asynchronous resource reclaimation is finished, so | 596 } |
| 269 // just change states and release this instance to delete ourself. | 597 |
| 270 if (state_ == STATE_ERROR) { | 598 float AlsaPcmOutputStream::SharedData::volume() { |
| 271 Release(); | |
| 272 state_ = STATE_CLOSED; | |
| 273 return; | |
| 274 } | |
| 275 | |
| 276 // Otherwise, cleanup as necessary. | |
| 277 if (state_ == STATE_CLOSED || state_ == STATE_CLOSING) { | |
| 278 NOTREACHED() << "Attempting to close twice."; | |
| 279 return; | |
| 280 } | |
| 281 | |
| 282 // If the stream is still running, stop it. | |
| 283 if (state_ == STATE_STARTED) { | |
| 284 StopInternal_Locked(); | |
| 285 } | |
| 286 | |
| 287 // If it is stopped (we may have just transitioned here in the previous if | |
| 288 // block), check if the resources have been released. If they have, | |
| 289 // transition immediately to STATE_CLOSED. Otherwise, move to | |
| 290 // STATE_CLOSING, and the ReleaseResources() task will move to STATE_CLOSED | |
| 291 // for us. | |
| 292 // | |
| 293 // If the stream has been stopped, close. | |
| 294 if (state_ == STATE_STOPPED) { | |
| 295 if (resources_released_) { | |
| 296 state_ = STATE_CLOSED; | |
| 297 } else { | |
| 298 state_ = STATE_CLOSING; | |
| 299 } | |
| 300 } else { | |
| 301 // TODO(ajwong): Can we safely handle state_ == STATE_CREATED? | |
| 302 NOTREACHED() << "Invalid state on close: " << state_; | |
| 303 // In release, just move to STATE_ERROR, and hope for the best. | |
| 304 EnterStateError_Locked(); | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 bool AlsaPCMOutputStream::CloseDevice_Locked() { | |
| 309 // Check the lock is held in a debug build. | |
| 310 DCHECK((lock_.AssertAcquired(), true)); | |
| 311 | |
| 312 int error = snd_pcm_close(playback_handle_); | |
| 313 if (error < 0) { | |
| 314 LOG(ERROR) << "Cannot close audio device (" << device_name_ << "): " | |
| 315 << snd_strerror(error); | |
| 316 return false; | |
| 317 } | |
| 318 | |
| 319 return true; | |
| 320 } | |
| 321 | |
| 322 void AlsaPCMOutputStream::ReleaseResources() { | |
| 323 AutoLock l(lock_); | 599 AutoLock l(lock_); |
| 324 | 600 return volume_; |
| 325 // Shutdown the audio device. | 601 } |
| 326 if (!CloseDevice_Locked()) { | 602 |
| 327 LOG(WARNING) << "Unable to close audio device. Leaking handle."; | 603 void AlsaPcmOutputStream::SharedData::set_volume(float v) { |
| 328 playback_handle_ = NULL; | |
| 329 } | |
| 330 | |
| 331 // Delete all the buffers. | |
| 332 STLDeleteElements(&buffered_packets_); | |
| 333 | |
| 334 // Release the source callback. | |
| 335 source_callback_->OnClose(this); | |
| 336 | |
| 337 // Shutdown the thread. | |
| 338 DCHECK_EQ(PlatformThread::CurrentId(), playback_thread_.thread_id()); | |
| 339 playback_thread_.message_loop()->Quit(); | |
| 340 | |
| 341 // TODO(ajwong): Do we need to join the playback thread? | |
| 342 | |
| 343 // If the stream is closing, then this function has just completed the last | |
| 344 // bit needed before closing. Transition to STATE_CLOSED. | |
| 345 if (state_ == STATE_CLOSING) { | |
| 346 state_ = STATE_CLOSED; | |
| 347 } | |
| 348 | |
| 349 // TODO(ajwong): Currently, the stream is leaked after the |playback_thread_| | |
| 350 // is stopped. Find a way to schedule its deletion on another thread, maybe | |
| 351 // using a DestructionObserver. | |
| 352 } | |
| 353 | |
| 354 snd_pcm_sframes_t AlsaPCMOutputStream::GetFramesOfDelay_Locked() { | |
| 355 // Check the lock is held in a debug build. | |
| 356 DCHECK((lock_.AssertAcquired(), true)); | |
| 357 | |
| 358 // Find the number of frames queued in the sound device. | |
| 359 snd_pcm_sframes_t delay_frames = 0; | |
| 360 int error = snd_pcm_delay(playback_handle_, &delay_frames); | |
| 361 if (error < 0) { | |
| 362 error = snd_pcm_recover(playback_handle_, | |
| 363 error /* Original error. */, | |
| 364 0 /* Silenty recover. */); | |
| 365 } | |
| 366 if (error < 0) { | |
| 367 LOG(ERROR) << "Could not query sound device for delay. Assuming 0: " | |
| 368 << snd_strerror(error); | |
| 369 } | |
| 370 | |
| 371 for (std::deque<Packet*>::const_iterator it = buffered_packets_.begin(); | |
| 372 it != buffered_packets_.end(); | |
| 373 ++it) { | |
| 374 delay_frames += ((*it)->size - (*it)->used) / bytes_per_frame_; | |
| 375 } | |
| 376 | |
| 377 return delay_frames; | |
| 378 } | |
| 379 | |
| 380 void AlsaPCMOutputStream::BufferPackets() { | |
| 381 AutoLock l(lock_); | 604 AutoLock l(lock_); |
| 382 | 605 volume_ = v; |
| 383 // Handle early outs for errored, stopped, or closing streams. | 606 } |
| 384 if (state_ == STATE_ERROR || | |
| 385 state_ == STATE_STOPPED || | |
| 386 state_ == STATE_CLOSING) { | |
| 387 return; | |
| 388 } | |
| 389 if (state_ != STATE_STARTED) { | |
| 390 NOTREACHED() << "Invalid stream state while buffering. " | |
| 391 << "Expected STATE_STARTED. Current state: " << state_; | |
| 392 return; | |
| 393 } | |
| 394 | |
| 395 // Early out if the buffer is already full. | |
| 396 snd_pcm_sframes_t delay_frames = GetFramesOfDelay_Locked(); | |
| 397 if (delay_frames < min_buffer_frames_) { | |
| 398 // Grab one packet. Drop the lock for the synchronous call. This will | |
| 399 // still stall the playback thread, but at least it will not block any | |
| 400 // other threads. | |
| 401 // | |
| 402 // TODO(ajwong): Move to cpu@'s non-blocking audio source. | |
| 403 scoped_ptr<Packet> packet; | |
| 404 size_t capacity = packet_size_; // Snag it for non-locked usage. | |
| 405 { | |
| 406 AutoUnlock synchronous_data_fetch(lock_); | |
| 407 packet.reset(new Packet(capacity)); | |
| 408 size_t used = source_callback_->OnMoreData(this, packet->buffer.get(), | |
| 409 packet->capacity); | |
| 410 CHECK(used <= capacity) << "Data source overran buffer. Aborting."; | |
| 411 packet->size = used; | |
| 412 media::AdjustVolume(packet->buffer.get(), packet->size, | |
| 413 channels_, bits_per_sample_ >> 3, | |
| 414 volume_); | |
| 415 // TODO(ajwong): Do more buffer validation here, like checking that the | |
| 416 // packet is correctly aligned to frames, etc. | |
| 417 } | |
| 418 // After reacquiring the lock, recheck state to make sure it is still | |
| 419 // STATE_STARTED. | |
| 420 if (state_ != STATE_STARTED) { | |
| 421 return; | |
| 422 } | |
| 423 buffered_packets_.push_back(packet.release()); | |
| 424 | |
| 425 // Recalculate delay frames. | |
| 426 delay_frames = GetFramesOfDelay_Locked(); | |
| 427 } | |
| 428 | |
| 429 // Since the current implementation of OnMoreData() blocks, only try to grab | |
| 430 // one packet per task. If the buffer is still too low, post another | |
| 431 // BufferPackets() task immediately. Otherwise, calculate when the buffer is | |
| 432 // likely to need filling and schedule a poll for the future. | |
| 433 int next_fill_time_ms = (delay_frames - min_buffer_frames_) / sample_rate_; | |
| 434 if (next_fill_time_ms <= kMinSleepMilliseconds) { | |
| 435 playback_thread_.message_loop()->PostTask( | |
| 436 FROM_HERE, | |
| 437 NewRunnableMethod(this, &AlsaPCMOutputStream::BufferPackets)); | |
| 438 } else { | |
| 439 // TODO(ajwong): Measure the reliability of the delay interval. Use | |
| 440 // base/histogram.h. | |
| 441 playback_thread_.message_loop()->PostDelayedTask( | |
| 442 FROM_HERE, | |
| 443 NewRunnableMethod(this, &AlsaPCMOutputStream::BufferPackets), | |
| 444 next_fill_time_ms); | |
| 445 } | |
| 446 | |
| 447 // If the |device_write_suspended_|, the audio device write tasks have | |
| 448 // stopped scheduling themselves due to an underrun of the in-memory buffer. | |
| 449 // Post a new task to restart it since we now have data. | |
| 450 if (device_write_suspended_) { | |
| 451 device_write_suspended_ = false; | |
| 452 playback_thread_.message_loop()->PostTask( | |
| 453 FROM_HERE, | |
| 454 NewRunnableMethod(this, &AlsaPCMOutputStream::FillAlsaDeviceBuffer)); | |
| 455 } | |
| 456 } | |
| 457 | |
| 458 void AlsaPCMOutputStream::FillAlsaDeviceBuffer() { | |
| 459 // TODO(ajwong): Try to move some of this code out from underneath the lock. | |
| 460 AutoLock l(lock_); | |
| 461 | |
| 462 // Find the number of frames that the device can accept right now. | |
| 463 snd_pcm_sframes_t device_buffer_frames_avail = | |
| 464 snd_pcm_avail_update(playback_handle_); | |
| 465 | |
| 466 // Write up to |device_buffer_frames_avail| frames to the ALSA device. | |
| 467 while (device_buffer_frames_avail > 0) { | |
| 468 if (buffered_packets_.empty()) { | |
| 469 device_write_suspended_ = true; | |
| 470 break; | |
| 471 } | |
| 472 | |
| 473 Packet* current_packet = buffered_packets_.front(); | |
| 474 | |
| 475 // Only process non 0-lengthed packets. | |
| 476 if (current_packet->used < current_packet->size) { | |
| 477 // Calculate the number of frames we have to write. | |
| 478 char* buffer_pos = current_packet->buffer.get() + current_packet->used; | |
| 479 snd_pcm_sframes_t buffer_frames = | |
| 480 (current_packet->size - current_packet->used) / | |
| 481 bytes_per_frame_; | |
| 482 snd_pcm_sframes_t frames_to_write = | |
| 483 std::min(buffer_frames, device_buffer_frames_avail); | |
| 484 | |
| 485 // Check that device_buffer_frames_avail isn't < 0. | |
| 486 DCHECK_GT(frames_to_write, 0); | |
| 487 | |
| 488 // Write it to the device. | |
| 489 int frames_written = | |
| 490 snd_pcm_writei(playback_handle_, buffer_pos, frames_to_write); | |
| 491 if (frames_written < 0) { | |
| 492 // Recover from EINTR, EPIPE (overrun/underrun), ESTRPIPE (stream | |
| 493 // suspended). | |
| 494 // | |
| 495 // TODO(ajwong): Check that we do not need to loop on recover, here and | |
| 496 // anywhere else we use recover. | |
| 497 frames_written = snd_pcm_recover(playback_handle_, | |
| 498 frames_written /* Original error. */, | |
| 499 0 /* Silenty recover. */); | |
| 500 } | |
| 501 if (frames_written < 0) { | |
| 502 LOG(ERROR) << "Failed to write to pcm device: " | |
| 503 << snd_strerror(frames_written); | |
| 504 ReleaseResources(); | |
| 505 EnterStateError_Locked(); | |
| 506 break; | |
| 507 } else { | |
| 508 current_packet->used += frames_written * bytes_per_frame_; | |
| 509 DCHECK_LE(current_packet->used, current_packet->size); | |
| 510 } | |
| 511 } | |
| 512 | |
| 513 if (current_packet->used >= current_packet->size) { | |
| 514 delete current_packet; | |
| 515 buffered_packets_.pop_front(); | |
| 516 } | |
| 517 } | |
| 518 | |
| 519 // If the memory buffer was not underrun, schedule another fill in the future. | |
| 520 if (!device_write_suspended_) { | |
| 521 playback_thread_.message_loop()->PostDelayedTask( | |
| 522 FROM_HERE, | |
| 523 NewRunnableMethod(this, &AlsaPCMOutputStream::FillAlsaDeviceBuffer), | |
| 524 kTargetLatencyMicroseconds / base::Time::kMicrosecondsPerMillisecond); | |
| 525 } | |
| 526 } | |
| 527 | |
| 528 void AlsaPCMOutputStream::SetVolume(double left_level, double right_level) { | |
| 529 AutoLock l(lock_); | |
| 530 volume_ = static_cast<float>(left_level); | |
| 531 } | |
| 532 | |
| 533 void AlsaPCMOutputStream::GetVolume(double* left_level, double* right_level) { | |
| 534 AutoLock l(lock_); | |
| 535 *left_level = volume_; | |
| 536 *right_level = volume_; | |
| 537 } | |
| OLD | NEW |