Chromium Code Reviews| 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 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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 stream_id_(0), |
| 51 play_on_start_(true), | 51 play_on_start_(true), |
| 52 is_started_(false) { | 52 is_started_(false), |
| 53 stopping_(false) { | |
| 53 CHECK(ipc_); | 54 CHECK(ipc_); |
| 54 } | 55 } |
| 55 | 56 |
| 56 void AudioOutputDevice::Initialize(const AudioParameters& params, | 57 void AudioOutputDevice::Initialize(const AudioParameters& params, |
| 57 RenderCallback* callback) { | 58 RenderCallback* callback) { |
| 58 CHECK_EQ(0, stream_id_) << | 59 CHECK_EQ(0, stream_id_) << |
| 59 "AudioOutputDevice::Initialize() must be called before Start()"; | 60 "AudioOutputDevice::Initialize() must be called before Start()"; |
| 60 | 61 |
| 61 CHECK(!callback_); // Calling Initialize() twice? | 62 CHECK(!callback_); // Calling Initialize() twice? |
| 62 | 63 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 83 DCHECK(callback_) << "Initialize hasn't been called"; | 84 DCHECK(callback_) << "Initialize hasn't been called"; |
| 84 message_loop()->PostTask(FROM_HERE, | 85 message_loop()->PostTask(FROM_HERE, |
| 85 base::Bind(&AudioOutputDevice::CreateStreamOnIOThread, this, | 86 base::Bind(&AudioOutputDevice::CreateStreamOnIOThread, this, |
| 86 audio_parameters_, input_channels_)); | 87 audio_parameters_, input_channels_)); |
| 87 } | 88 } |
| 88 | 89 |
| 89 void AudioOutputDevice::Stop() { | 90 void AudioOutputDevice::Stop() { |
| 90 { | 91 { |
| 91 base::AutoLock auto_lock(audio_thread_lock_); | 92 base::AutoLock auto_lock(audio_thread_lock_); |
| 92 audio_thread_.Stop(MessageLoop::current()); | 93 audio_thread_.Stop(MessageLoop::current()); |
| 94 stopping_ = true; | |
|
tommi (sloooow) - chröme
2012/09/20 15:00:22
instead of the variable, you can use audio_thread_
scherkus (not reviewing)
2012/09/20 17:48:28
Not quite... OnStreamCreated() DCHECKs for audio_t
| |
| 93 } | 95 } |
| 94 | 96 |
| 95 message_loop()->PostTask(FROM_HERE, | 97 message_loop()->PostTask(FROM_HERE, |
| 96 base::Bind(&AudioOutputDevice::ShutDownOnIOThread, this)); | 98 base::Bind(&AudioOutputDevice::ShutDownOnIOThread, this)); |
| 97 } | 99 } |
| 98 | 100 |
| 99 void AudioOutputDevice::Play() { | 101 void AudioOutputDevice::Play() { |
| 100 message_loop()->PostTask(FROM_HERE, | 102 message_loop()->PostTask(FROM_HERE, |
| 101 base::Bind(&AudioOutputDevice::PlayOnIOThread, this)); | 103 base::Bind(&AudioOutputDevice::PlayOnIOThread, this)); |
| 102 } | 104 } |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 159 is_started_ = false; | 161 is_started_ = false; |
| 160 | 162 |
| 161 if (ipc_) { | 163 if (ipc_) { |
| 162 ipc_->CloseStream(stream_id_); | 164 ipc_->CloseStream(stream_id_); |
| 163 ipc_->RemoveDelegate(stream_id_); | 165 ipc_->RemoveDelegate(stream_id_); |
| 164 } | 166 } |
| 165 | 167 |
| 166 stream_id_ = 0; | 168 stream_id_ = 0; |
| 167 } | 169 } |
| 168 | 170 |
| 169 // We can run into an issue where ShutDownOnIOThread is called right after | |
| 170 // OnStreamCreated is called in cases where Start/Stop are called before we | |
| 171 // get the OnStreamCreated callback. To handle that corner case, we call | |
| 172 // Stop(). In most cases, the thread will already be stopped. | |
| 173 // Another situation is when the IO thread goes away before Stop() is called | 171 // Another situation is when the IO thread goes away before Stop() is called |
| 174 // in which case, we cannot use the message loop to close the thread handle | 172 // in which case, we cannot use the message loop to close the thread handle |
| 175 // and can't not rely on the main thread existing either. | 173 // and can't rely on the main thread existing either. |
| 174 base::AutoLock auto_lock_(audio_thread_lock_); | |
| 176 base::ThreadRestrictions::ScopedAllowIO allow_io; | 175 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 177 audio_thread_.Stop(NULL); | 176 audio_thread_.Stop(NULL); |
| 178 audio_callback_.reset(); | 177 audio_callback_.reset(); |
| 178 stopping_ = false; | |
|
tommi (sloooow) - chröme
2012/09/20 15:00:22
this is one place that's functionally different fr
| |
| 179 } | 179 } |
| 180 | 180 |
| 181 void AudioOutputDevice::SetVolumeOnIOThread(double volume) { | 181 void AudioOutputDevice::SetVolumeOnIOThread(double volume) { |
| 182 DCHECK(message_loop()->BelongsToCurrentThread()); | 182 DCHECK(message_loop()->BelongsToCurrentThread()); |
| 183 if (stream_id_) | 183 if (stream_id_) |
| 184 ipc_->SetVolume(stream_id_, volume); | 184 ipc_->SetVolume(stream_id_, volume); |
| 185 } | 185 } |
| 186 | 186 |
| 187 void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegate::State state) { | 187 void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegate::State state) { |
| 188 DCHECK(message_loop()->BelongsToCurrentThread()); | 188 DCHECK(message_loop()->BelongsToCurrentThread()); |
| 189 | 189 |
| 190 // Do nothing if the stream has been closed. | 190 // Do nothing if the stream has been closed. |
| 191 if (!stream_id_) | 191 if (!stream_id_) |
| 192 return; | 192 return; |
| 193 | 193 |
| 194 if (state == AudioOutputIPCDelegate::kError) { | 194 if (state == AudioOutputIPCDelegate::kError) { |
| 195 DLOG(WARNING) << "AudioOutputDevice::OnStateChanged(kError)"; | 195 DLOG(WARNING) << "AudioOutputDevice::OnStateChanged(kError)"; |
| 196 // Don't dereference the callback object if the audio thread | 196 // Don't dereference the callback object if the audio thread |
| 197 // is stopped or stopping. That could mean that the callback | 197 // is stopped or stopping. That could mean that the callback |
| 198 // object has been deleted. | 198 // object has been deleted. |
| 199 // TODO(tommi): Add an explicit contract for clearing the callback | 199 // TODO(tommi): Add an explicit contract for clearing the callback |
| 200 // object. Possibly require calling Initialize again or provide | 200 // object. Possibly require calling Initialize again or provide |
| 201 // a callback object via Start() and clear it in Stop(). | 201 // a callback object via Start() and clear it in Stop(). |
| 202 base::AutoLock auto_lock_(audio_thread_lock_); | |
|
tommi (sloooow) - chröme
2012/09/20 15:00:22
this isn't needed. IsStopped() does locking intern
scherkus (not reviewing)
2012/09/20 17:48:28
Done.
| |
| 202 if (!audio_thread_.IsStopped()) | 203 if (!audio_thread_.IsStopped()) |
| 203 callback_->OnRenderError(); | 204 callback_->OnRenderError(); |
| 204 } | 205 } |
| 205 } | 206 } |
| 206 | 207 |
| 207 void AudioOutputDevice::OnStreamCreated( | 208 void AudioOutputDevice::OnStreamCreated( |
| 208 base::SharedMemoryHandle handle, | 209 base::SharedMemoryHandle handle, |
| 209 base::SyncSocket::Handle socket_handle, | 210 base::SyncSocket::Handle socket_handle, |
| 210 int length) { | 211 int length) { |
| 211 DCHECK(message_loop()->BelongsToCurrentThread()); | 212 DCHECK(message_loop()->BelongsToCurrentThread()); |
| 212 #if defined(OS_WIN) | 213 #if defined(OS_WIN) |
| 213 DCHECK(handle); | 214 DCHECK(handle); |
| 214 DCHECK(socket_handle); | 215 DCHECK(socket_handle); |
| 215 #else | 216 #else |
| 216 DCHECK_GE(handle.fd, 0); | 217 DCHECK_GE(handle.fd, 0); |
| 217 DCHECK_GE(socket_handle, 0); | 218 DCHECK_GE(socket_handle, 0); |
| 218 #endif | 219 #endif |
| 219 | 220 |
| 220 // We should only get this callback if stream_id_ is valid. If it is not, | 221 // We should only get this callback if stream_id_ is valid. If it is not, |
| 221 // the IPC layer should have closed the shared memory and socket handles | 222 // the IPC layer should have closed the shared memory and socket handles |
| 222 // for us and not invoked the callback. The basic assertion is that when | 223 // for us and not invoked the callback. The basic assertion is that when |
| 223 // stream_id_ is 0 the AudioOutputDevice instance is not registered as a | 224 // stream_id_ is 0 the AudioOutputDevice instance is not registered as a |
| 224 // delegate and hence it should not receive callbacks. | 225 // delegate and hence it should not receive callbacks. |
| 225 DCHECK(stream_id_); | 226 DCHECK(stream_id_); |
| 226 | 227 |
| 228 // Stop() can stop |audio_thread_| before we receive OnStreamCreated(). | |
|
henrika (OOO until Aug 14)
2012/09/20 07:54:52
Sorry but this sentence is a bit tricky to underst
scherkus (not reviewing)
2012/09/20 17:48:28
Done.
| |
| 229 // In that case we don't risk starting the thread using |callback_|, which | |
| 230 // may have point to freed memory. | |
|
tommi (sloooow) - chröme
2012/09/20 15:00:22
If AudioDeviceThread::Stop() has been called, then
scherkus (not reviewing)
2012/09/20 17:48:28
I like all of those ideas but considering this has
| |
| 231 // | |
| 232 // TODO(scherkus): The real fix is to have sane ownership semantics. The fact | |
|
henrika (OOO until Aug 14)
2012/09/20 07:54:52
Seems like a very good idea to add a link to crbug
scherkus (not reviewing)
2012/09/20 17:48:28
Done.
| |
| 233 // that |callback_| (which should own and outlive this object!) can point to | |
| 234 // freed memory is a mess. AudioRendererSink should be non-refcounted that | |
|
henrika (OOO until Aug 14)
2012/09/20 07:54:52
"that" -> "so that"
scherkus (not reviewing)
2012/09/20 17:48:28
Done.
| |
| 235 // owners (WebRtcAudioDeviceImpl, AudioRendererImpl, etc...) can Stop() and | |
| 236 // delete as they see fit. AudioOutputDevice should internally use WeakPtr | |
| 237 // to handle teardown and thread hopping. | |
| 227 base::AutoLock auto_lock(audio_thread_lock_); | 238 base::AutoLock auto_lock(audio_thread_lock_); |
| 239 if (stopping_) | |
|
tommi (sloooow) - chröme
2012/09/20 15:00:22
If we change the Start() vs Initialize() contract,
scherkus (not reviewing)
2012/09/20 17:48:28
I prototyped a change and it appears webrtc can us
| |
| 240 return; | |
| 228 | 241 |
| 229 DCHECK(audio_thread_.IsStopped()); | 242 DCHECK(audio_thread_.IsStopped()); |
| 230 audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback( | 243 audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback( |
| 231 audio_parameters_, input_channels_, handle, length, callback_)); | 244 audio_parameters_, input_channels_, handle, length, callback_)); |
| 232 audio_thread_.Start(audio_callback_.get(), socket_handle, | 245 audio_thread_.Start(audio_callback_.get(), socket_handle, |
| 233 "AudioOutputDevice"); | 246 "AudioOutputDevice"); |
| 234 | 247 |
| 235 // We handle the case where Play() and/or Pause() may have been called | 248 // We handle the case where Play() and/or Pause() may have been called |
| 236 // multiple times before OnStreamCreated() gets called. | 249 // multiple times before OnStreamCreated() gets called. |
| 237 is_started_ = true; | 250 is_started_ = true; |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 323 // TODO(dalecurtis): Technically this is not always correct. Due to channel | 336 // TODO(dalecurtis): Technically this is not always correct. Due to channel |
| 324 // padding for alignment, there may be more data available than this. We're | 337 // padding for alignment, there may be more data available than this. We're |
| 325 // relying on AudioSyncReader::Read() to parse this with that in mind. Rename | 338 // relying on AudioSyncReader::Read() to parse this with that in mind. Rename |
| 326 // these methods to Set/GetActualFrameCount(). | 339 // these methods to Set/GetActualFrameCount(). |
| 327 SetActualDataSizeInBytes( | 340 SetActualDataSizeInBytes( |
| 328 &shared_memory_, memory_length_, | 341 &shared_memory_, memory_length_, |
| 329 num_frames * sizeof(*output_bus_->channel(0)) * output_bus_->channels()); | 342 num_frames * sizeof(*output_bus_->channel(0)) * output_bus_->channels()); |
| 330 } | 343 } |
| 331 | 344 |
| 332 } // namespace media. | 345 } // namespace media. |
| OLD | NEW |