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

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

Issue 160497: Reimplement the AlsaPcmOutputStream and fix the threading issues. (Closed)
Patch Set: Address Andrew's comments. Diff aginst Patch 8 please. Created 11 years, 4 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 | « media/audio/linux/alsa_output.h ('k') | media/audio/linux/alsa_output_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 }
OLDNEW
« no previous file with comments | « media/audio/linux/alsa_output.h ('k') | media/audio/linux/alsa_output_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698