OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "media/audio/audio_output_device.h" | 5 #include "media/audio/audio_output_device.h" |
6 | 6 |
7 #include "base/debug/trace_event.h" | 7 #include "base/debug/trace_event.h" |
8 #include "base/message_loop.h" | 8 #include "base/message_loop.h" |
9 #include "base/threading/thread_restrictions.h" | 9 #include "base/threading/thread_restrictions.h" |
10 #include "base/time.h" | 10 #include "base/time.h" |
(...skipping 29 matching lines...) Expand all Loading... | |
40 DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback); | 40 DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback); |
41 }; | 41 }; |
42 | 42 |
43 AudioOutputDevice::AudioOutputDevice( | 43 AudioOutputDevice::AudioOutputDevice( |
44 AudioOutputIPC* ipc, | 44 AudioOutputIPC* ipc, |
45 const scoped_refptr<base::MessageLoopProxy>& io_loop) | 45 const scoped_refptr<base::MessageLoopProxy>& io_loop) |
46 : ScopedLoopObserver(io_loop), | 46 : ScopedLoopObserver(io_loop), |
47 input_channels_(0), | 47 input_channels_(0), |
48 callback_(NULL), | 48 callback_(NULL), |
49 ipc_(ipc), | 49 ipc_(ipc), |
50 stream_id_(0), | 50 state_(IDLE), |
51 play_on_start_(true), | 51 play_on_start_(true), |
52 is_started_(false), | |
53 stopping_hack_(false) { | 52 stopping_hack_(false) { |
54 CHECK(ipc_); | 53 CHECK(ipc_); |
54 stream_id_ = ipc_->AddDelegate(this); | |
55 } | |
56 | |
57 int AudioOutputDevice::stream_id() const { | |
58 return stream_id_; | |
55 } | 59 } |
56 | 60 |
57 void AudioOutputDevice::Initialize(const AudioParameters& params, | 61 void AudioOutputDevice::Initialize(const AudioParameters& params, |
58 RenderCallback* callback) { | 62 RenderCallback* callback) { |
59 CHECK_EQ(0, stream_id_) << | 63 DCHECK(!callback_) << "Calling Initialize() twice?"; |
60 "AudioOutputDevice::Initialize() must be called before Start()"; | |
61 | |
62 CHECK(!callback_); // Calling Initialize() twice? | |
63 | |
64 audio_parameters_ = params; | 64 audio_parameters_ = params; |
65 callback_ = callback; | 65 callback_ = callback; |
66 } | 66 } |
67 | 67 |
68 void AudioOutputDevice::InitializeIO(const AudioParameters& params, | 68 void AudioOutputDevice::InitializeIO(const AudioParameters& params, |
69 int input_channels, | 69 int input_channels, |
70 RenderCallback* callback) { | 70 RenderCallback* callback) { |
71 DCHECK_GE(input_channels, 0); | 71 DCHECK_GE(input_channels, 0); |
72 DCHECK_LT(input_channels, limits::kMaxChannels); | 72 DCHECK_LT(input_channels, limits::kMaxChannels); |
73 input_channels_ = input_channels; | 73 input_channels_ = input_channels; |
74 Initialize(params, callback); | 74 Initialize(params, callback); |
75 } | 75 } |
76 | 76 |
77 AudioOutputDevice::~AudioOutputDevice() { | 77 AudioOutputDevice::~AudioOutputDevice() { |
78 // The current design requires that the user calls Stop() before deleting | 78 // The current design requires that the user calls Stop() before deleting |
79 // this class. | 79 // this class. |
80 CHECK_EQ(0, stream_id_); | 80 DCHECK(audio_thread_.IsStopped()); |
81 | |
82 if (ipc_) | |
83 ipc_->RemoveDelegate(stream_id_); | |
81 } | 84 } |
82 | 85 |
83 void AudioOutputDevice::Start() { | 86 void AudioOutputDevice::Start() { |
84 DCHECK(callback_) << "Initialize hasn't been called"; | 87 DCHECK(callback_) << "Initialize hasn't been called"; |
85 message_loop()->PostTask(FROM_HERE, | 88 message_loop()->PostTask(FROM_HERE, |
86 base::Bind(&AudioOutputDevice::CreateStreamOnIOThread, this, | 89 base::Bind(&AudioOutputDevice::CreateStreamOnIOThread, this, |
87 audio_parameters_, input_channels_)); | 90 audio_parameters_, input_channels_)); |
88 } | 91 } |
89 | 92 |
90 void AudioOutputDevice::Stop() { | 93 void AudioOutputDevice::Stop() { |
(...skipping 25 matching lines...) Expand all Loading... | |
116 base::Bind(&AudioOutputDevice::SetVolumeOnIOThread, this, volume))) { | 119 base::Bind(&AudioOutputDevice::SetVolumeOnIOThread, this, volume))) { |
117 return false; | 120 return false; |
118 } | 121 } |
119 | 122 |
120 return true; | 123 return true; |
121 } | 124 } |
122 | 125 |
123 void AudioOutputDevice::CreateStreamOnIOThread(const AudioParameters& params, | 126 void AudioOutputDevice::CreateStreamOnIOThread(const AudioParameters& params, |
124 int input_channels) { | 127 int input_channels) { |
125 DCHECK(message_loop()->BelongsToCurrentThread()); | 128 DCHECK(message_loop()->BelongsToCurrentThread()); |
126 // Make sure we don't create the stream more than once. | 129 if (state_ == IDLE && ipc_) { |
DaleCurtis
2012/11/28 19:29:50
Seems unnecessary to check ipc_ since you always s
miu
2012/11/28 20:59:03
Done.
| |
127 DCHECK_EQ(0, stream_id_); | 130 state_ = CREATING_STREAM; |
128 if (stream_id_) | 131 ipc_->CreateStream(stream_id_, params, input_channels); |
129 return; | 132 } |
130 | |
131 stream_id_ = ipc_->AddDelegate(this); | |
132 ipc_->CreateStream(stream_id_, params, input_channels); | |
133 } | 133 } |
134 | 134 |
135 void AudioOutputDevice::PlayOnIOThread() { | 135 void AudioOutputDevice::PlayOnIOThread() { |
136 DCHECK(message_loop()->BelongsToCurrentThread()); | 136 DCHECK(message_loop()->BelongsToCurrentThread()); |
137 if (stream_id_ && is_started_) | 137 if (state_ == PAUSED && ipc_) { |
138 ipc_->PlayStream(stream_id_); | 138 ipc_->PlayStream(stream_id_); |
139 else | 139 state_ = PLAYING; |
140 play_on_start_ = false; | |
141 } else { | |
140 play_on_start_ = true; | 142 play_on_start_ = true; |
143 } | |
141 } | 144 } |
142 | 145 |
143 void AudioOutputDevice::PauseOnIOThread(bool flush) { | 146 void AudioOutputDevice::PauseOnIOThread(bool flush) { |
144 DCHECK(message_loop()->BelongsToCurrentThread()); | 147 DCHECK(message_loop()->BelongsToCurrentThread()); |
145 if (stream_id_ && is_started_) { | 148 if (state_ == PLAYING) { |
146 ipc_->PauseStream(stream_id_); | 149 if (ipc_) { |
DaleCurtis
2012/11/28 19:29:50
ditto.
miu
2012/11/28 20:59:03
Done.
| |
147 if (flush) | 150 ipc_->PauseStream(stream_id_); |
148 ipc_->FlushStream(stream_id_); | 151 if (flush) |
152 ipc_->FlushStream(stream_id_); | |
153 } | |
154 state_ = PAUSED; | |
149 } else { | 155 } else { |
150 // Note that |flush| isn't relevant here since this is the case where | 156 // Note that |flush| isn't relevant here since this is the case where |
151 // the stream is first starting. | 157 // the stream is first starting. |
152 play_on_start_ = false; | |
153 } | 158 } |
159 play_on_start_ = false; | |
DaleCurtis
2012/11/28 19:29:50
Why move this? it's already false from PlayOnIOThr
miu
2012/11/28 20:59:03
Example: What if we're in state CREATING_STREAM (w
| |
154 } | 160 } |
155 | 161 |
156 void AudioOutputDevice::ShutDownOnIOThread() { | 162 void AudioOutputDevice::ShutDownOnIOThread() { |
157 DCHECK(message_loop()->BelongsToCurrentThread()); | 163 DCHECK(message_loop()->BelongsToCurrentThread()); |
158 | 164 |
159 // Make sure we don't call shutdown more than once. | 165 // Make sure we don't call shutdown more than once. |
160 if (stream_id_) { | 166 if (state_ >= CREATING_STREAM) { |
DaleCurtis
2012/11/28 19:29:50
Seems fragile. Though a switch statement here and
miu
2012/11/28 20:59:03
Yeah. I added a comment in the header file to war
| |
161 is_started_ = false; | 167 if (ipc_) |
DaleCurtis
2012/11/28 19:29:50
ditto.
miu
2012/11/28 20:59:03
Done.
| |
162 | |
163 if (ipc_) { | |
164 ipc_->CloseStream(stream_id_); | 168 ipc_->CloseStream(stream_id_); |
165 ipc_->RemoveDelegate(stream_id_); | 169 state_ = IDLE; |
166 } | |
167 | |
168 stream_id_ = 0; | |
169 } | 170 } |
170 | 171 |
171 // We can run into an issue where ShutDownOnIOThread is called right after | 172 // We can run into an issue where ShutDownOnIOThread is called right after |
172 // OnStreamCreated is called in cases where Start/Stop are called before we | 173 // OnStreamCreated is called in cases where Start/Stop are called before we |
173 // get the OnStreamCreated callback. To handle that corner case, we call | 174 // get the OnStreamCreated callback. To handle that corner case, we call |
174 // Stop(). In most cases, the thread will already be stopped. | 175 // Stop(). In most cases, the thread will already be stopped. |
175 // | 176 // |
176 // Another situation is when the IO thread goes away before Stop() is called | 177 // Another situation is when the IO thread goes away before Stop() is called |
177 // in which case, we cannot use the message loop to close the thread handle | 178 // in which case, we cannot use the message loop to close the thread handle |
178 // and can't rely on the main thread existing either. | 179 // and can't rely on the main thread existing either. |
179 base::AutoLock auto_lock_(audio_thread_lock_); | 180 base::AutoLock auto_lock_(audio_thread_lock_); |
180 base::ThreadRestrictions::ScopedAllowIO allow_io; | 181 base::ThreadRestrictions::ScopedAllowIO allow_io; |
181 audio_thread_.Stop(NULL); | 182 audio_thread_.Stop(NULL); |
182 audio_callback_.reset(); | 183 audio_callback_.reset(); |
183 stopping_hack_ = false; | 184 stopping_hack_ = false; |
184 } | 185 } |
185 | 186 |
186 void AudioOutputDevice::SetVolumeOnIOThread(double volume) { | 187 void AudioOutputDevice::SetVolumeOnIOThread(double volume) { |
187 DCHECK(message_loop()->BelongsToCurrentThread()); | 188 DCHECK(message_loop()->BelongsToCurrentThread()); |
188 if (stream_id_) | 189 if (state_ >= PAUSED && ipc_) |
189 ipc_->SetVolume(stream_id_, volume); | 190 ipc_->SetVolume(stream_id_, volume); |
190 } | 191 } |
191 | 192 |
192 void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegate::State state) { | 193 void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegate::State state) { |
193 DCHECK(message_loop()->BelongsToCurrentThread()); | 194 DCHECK(message_loop()->BelongsToCurrentThread()); |
194 | 195 |
195 // Do nothing if the stream has been closed. | 196 // Do nothing if the stream has been closed. |
196 if (!stream_id_) | 197 if (state_ < CREATING_STREAM) |
197 return; | 198 return; |
198 | 199 |
199 if (state == AudioOutputIPCDelegate::kError) { | 200 if (state == AudioOutputIPCDelegate::kError) { |
200 DLOG(WARNING) << "AudioOutputDevice::OnStateChanged(kError)"; | 201 DLOG(WARNING) << "AudioOutputDevice::OnStateChanged(kError)"; |
201 // Don't dereference the callback object if the audio thread | 202 // Don't dereference the callback object if the audio thread |
202 // is stopped or stopping. That could mean that the callback | 203 // is stopped or stopping. That could mean that the callback |
203 // object has been deleted. | 204 // object has been deleted. |
204 // TODO(tommi): Add an explicit contract for clearing the callback | 205 // TODO(tommi): Add an explicit contract for clearing the callback |
205 // object. Possibly require calling Initialize again or provide | 206 // object. Possibly require calling Initialize again or provide |
206 // a callback object via Start() and clear it in Stop(). | 207 // a callback object via Start() and clear it in Stop(). |
207 if (!audio_thread_.IsStopped()) | 208 if (!audio_thread_.IsStopped()) |
208 callback_->OnRenderError(); | 209 callback_->OnRenderError(); |
209 } | 210 } |
210 } | 211 } |
211 | 212 |
212 void AudioOutputDevice::OnStreamCreated( | 213 void AudioOutputDevice::OnStreamCreated( |
213 base::SharedMemoryHandle handle, | 214 base::SharedMemoryHandle handle, |
214 base::SyncSocket::Handle socket_handle, | 215 base::SyncSocket::Handle socket_handle, |
215 int length) { | 216 int length) { |
216 DCHECK(message_loop()->BelongsToCurrentThread()); | 217 DCHECK(message_loop()->BelongsToCurrentThread()); |
217 #if defined(OS_WIN) | 218 #if defined(OS_WIN) |
218 DCHECK(handle); | 219 DCHECK(handle); |
219 DCHECK(socket_handle); | 220 DCHECK(socket_handle); |
220 #else | 221 #else |
221 DCHECK_GE(handle.fd, 0); | 222 DCHECK_GE(handle.fd, 0); |
222 DCHECK_GE(socket_handle, 0); | 223 DCHECK_GE(socket_handle, 0); |
223 #endif | 224 #endif |
224 | 225 |
225 // We should only get this callback if stream_id_ is valid. If it is not, | 226 if (state_ != CREATING_STREAM) |
226 // the IPC layer should have closed the shared memory and socket handles | 227 return; |
227 // for us and not invoked the callback. The basic assertion is that when | |
228 // stream_id_ is 0 the AudioOutputDevice instance is not registered as a | |
229 // delegate and hence it should not receive callbacks. | |
230 DCHECK(stream_id_); | |
231 | 228 |
232 // We can receive OnStreamCreated() on the IO thread after the client has | 229 // We can receive OnStreamCreated() on the IO thread after the client has |
233 // called Stop() but before ShutDownOnIOThread() is processed. In such a | 230 // called Stop() but before ShutDownOnIOThread() is processed. In such a |
234 // situation |callback_| might point to freed memory. Instead of starting | 231 // situation |callback_| might point to freed memory. Instead of starting |
235 // |audio_thread_| do nothing and wait for ShutDownOnIOThread() to get called. | 232 // |audio_thread_| do nothing and wait for ShutDownOnIOThread() to get called. |
236 // | 233 // |
237 // TODO(scherkus): The real fix is to have sane ownership semantics. The fact | 234 // TODO(scherkus): The real fix is to have sane ownership semantics. The fact |
238 // that |callback_| (which should own and outlive this object!) can point to | 235 // that |callback_| (which should own and outlive this object!) can point to |
239 // freed memory is a mess. AudioRendererSink should be non-refcounted so that | 236 // freed memory is a mess. AudioRendererSink should be non-refcounted so that |
240 // owners (WebRtcAudioDeviceImpl, AudioRendererImpl, etc...) can Stop() and | 237 // owners (WebRtcAudioDeviceImpl, AudioRendererImpl, etc...) can Stop() and |
241 // delete as they see fit. AudioOutputDevice should internally use WeakPtr | 238 // delete as they see fit. AudioOutputDevice should internally use WeakPtr |
242 // to handle teardown and thread hopping. See http://crbug.com/151051 for | 239 // to handle teardown and thread hopping. See http://crbug.com/151051 for |
243 // details. | 240 // details. |
244 base::AutoLock auto_lock(audio_thread_lock_); | 241 base::AutoLock auto_lock(audio_thread_lock_); |
245 if (stopping_hack_) | 242 if (stopping_hack_) |
246 return; | 243 return; |
247 | 244 |
248 DCHECK(audio_thread_.IsStopped()); | 245 DCHECK(audio_thread_.IsStopped()); |
249 audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback( | 246 audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback( |
250 audio_parameters_, input_channels_, handle, length, callback_)); | 247 audio_parameters_, input_channels_, handle, length, callback_)); |
251 audio_thread_.Start(audio_callback_.get(), socket_handle, | 248 audio_thread_.Start(audio_callback_.get(), socket_handle, |
252 "AudioOutputDevice"); | 249 "AudioOutputDevice"); |
250 state_ = PAUSED; | |
253 | 251 |
254 // We handle the case where Play() and/or Pause() may have been called | 252 // We handle the case where Play() and/or Pause() may have been called |
255 // multiple times before OnStreamCreated() gets called. | 253 // multiple times before OnStreamCreated() gets called. |
256 is_started_ = true; | |
257 if (play_on_start_) | 254 if (play_on_start_) |
258 PlayOnIOThread(); | 255 PlayOnIOThread(); |
259 } | 256 } |
260 | 257 |
261 void AudioOutputDevice::OnIPCClosed() { | 258 void AudioOutputDevice::OnIPCClosed() { |
259 DCHECK(message_loop()->BelongsToCurrentThread()); | |
260 state_ = IPC_CLOSED; | |
262 ipc_ = NULL; | 261 ipc_ = NULL; |
263 } | 262 } |
264 | 263 |
265 void AudioOutputDevice::WillDestroyCurrentMessageLoop() { | 264 void AudioOutputDevice::WillDestroyCurrentMessageLoop() { |
266 LOG(ERROR) << "IO loop going away before the audio device has been stopped"; | 265 LOG(ERROR) << "IO loop going away before the audio device has been stopped"; |
267 ShutDownOnIOThread(); | 266 ShutDownOnIOThread(); |
268 } | 267 } |
269 | 268 |
270 // AudioOutputDevice::AudioThreadCallback | 269 // AudioOutputDevice::AudioThreadCallback |
271 | 270 |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
342 // TODO(dalecurtis): Technically this is not always correct. Due to channel | 341 // TODO(dalecurtis): Technically this is not always correct. Due to channel |
343 // padding for alignment, there may be more data available than this. We're | 342 // padding for alignment, there may be more data available than this. We're |
344 // relying on AudioSyncReader::Read() to parse this with that in mind. Rename | 343 // relying on AudioSyncReader::Read() to parse this with that in mind. Rename |
345 // these methods to Set/GetActualFrameCount(). | 344 // these methods to Set/GetActualFrameCount(). |
346 SetActualDataSizeInBytes( | 345 SetActualDataSizeInBytes( |
347 &shared_memory_, memory_length_, | 346 &shared_memory_, memory_length_, |
348 num_frames * sizeof(*output_bus_->channel(0)) * output_bus_->channels()); | 347 num_frames * sizeof(*output_bus_->channel(0)) * output_bus_->channels()); |
349 } | 348 } |
350 | 349 |
351 } // namespace media. | 350 } // namespace media. |
OLD | NEW |