| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/renderer_host/audio_renderer_host.h" | |
| 6 | |
| 7 #include "base/metrics/histogram.h" | |
| 8 #include "base/process.h" | |
| 9 #include "base/shared_memory.h" | |
| 10 #include "chrome/browser/renderer_host/audio_sync_reader.h" | |
| 11 #include "chrome/common/render_messages.h" | |
| 12 #include "chrome/common/render_messages_params.h" | |
| 13 #include "ipc/ipc_logging.h" | |
| 14 | |
| 15 // The minimum number of samples in a hardware packet. | |
| 16 // This value is selected so that we can handle down to 5khz sample rate. | |
| 17 static const int kMinSamplesPerHardwarePacket = 1024; | |
| 18 | |
| 19 // The maximum number of samples in a hardware packet. | |
| 20 // This value is selected so that we can handle up to 192khz sample rate. | |
| 21 static const int kMaxSamplesPerHardwarePacket = 64 * 1024; | |
| 22 | |
| 23 // This constant governs the hardware audio buffer size, this value should be | |
| 24 // chosen carefully. | |
| 25 // This value is selected so that we have 8192 samples for 48khz streams. | |
| 26 static const int kMillisecondsPerHardwarePacket = 170; | |
| 27 | |
| 28 static uint32 SelectSamplesPerPacket(AudioParameters params) { | |
| 29 // Select the number of samples that can provide at least | |
| 30 // |kMillisecondsPerHardwarePacket| worth of audio data. | |
| 31 int samples = kMinSamplesPerHardwarePacket; | |
| 32 while (samples <= kMaxSamplesPerHardwarePacket && | |
| 33 samples * base::Time::kMillisecondsPerSecond < | |
| 34 params.sample_rate * kMillisecondsPerHardwarePacket) { | |
| 35 samples *= 2; | |
| 36 } | |
| 37 return samples; | |
| 38 } | |
| 39 | |
| 40 AudioRendererHost::AudioEntry::AudioEntry() | |
| 41 : render_view_id(0), | |
| 42 stream_id(0), | |
| 43 pending_buffer_request(false), | |
| 44 pending_close(false) { | |
| 45 } | |
| 46 | |
| 47 AudioRendererHost::AudioEntry::~AudioEntry() {} | |
| 48 | |
| 49 /////////////////////////////////////////////////////////////////////////////// | |
| 50 // AudioRendererHost implementations. | |
| 51 AudioRendererHost::AudioRendererHost() { | |
| 52 } | |
| 53 | |
| 54 AudioRendererHost::~AudioRendererHost() { | |
| 55 DCHECK(audio_entries_.empty()); | |
| 56 } | |
| 57 | |
| 58 void AudioRendererHost::OnChannelClosing() { | |
| 59 BrowserMessageFilter::OnChannelClosing(); | |
| 60 | |
| 61 // Since the IPC channel is gone, close all requested audio streams. | |
| 62 DeleteEntries(); | |
| 63 } | |
| 64 | |
| 65 void AudioRendererHost::OnDestruct() const { | |
| 66 BrowserThread::DeleteOnIOThread::Destruct(this); | |
| 67 } | |
| 68 | |
| 69 /////////////////////////////////////////////////////////////////////////////// | |
| 70 // media::AudioOutputController::EventHandler implementations. | |
| 71 void AudioRendererHost::OnCreated(media::AudioOutputController* controller) { | |
| 72 BrowserThread::PostTask( | |
| 73 BrowserThread::IO, | |
| 74 FROM_HERE, | |
| 75 NewRunnableMethod( | |
| 76 this, | |
| 77 &AudioRendererHost::DoCompleteCreation, | |
| 78 make_scoped_refptr(controller))); | |
| 79 } | |
| 80 | |
| 81 void AudioRendererHost::OnPlaying(media::AudioOutputController* controller) { | |
| 82 BrowserThread::PostTask( | |
| 83 BrowserThread::IO, | |
| 84 FROM_HERE, | |
| 85 NewRunnableMethod( | |
| 86 this, | |
| 87 &AudioRendererHost::DoSendPlayingMessage, | |
| 88 make_scoped_refptr(controller))); | |
| 89 } | |
| 90 | |
| 91 void AudioRendererHost::OnPaused(media::AudioOutputController* controller) { | |
| 92 BrowserThread::PostTask( | |
| 93 BrowserThread::IO, | |
| 94 FROM_HERE, | |
| 95 NewRunnableMethod( | |
| 96 this, | |
| 97 &AudioRendererHost::DoSendPausedMessage, | |
| 98 make_scoped_refptr(controller))); | |
| 99 } | |
| 100 | |
| 101 void AudioRendererHost::OnError(media::AudioOutputController* controller, | |
| 102 int error_code) { | |
| 103 BrowserThread::PostTask( | |
| 104 BrowserThread::IO, | |
| 105 FROM_HERE, | |
| 106 NewRunnableMethod(this, | |
| 107 &AudioRendererHost::DoHandleError, | |
| 108 make_scoped_refptr(controller), | |
| 109 error_code)); | |
| 110 } | |
| 111 | |
| 112 void AudioRendererHost::OnMoreData(media::AudioOutputController* controller, | |
| 113 AudioBuffersState buffers_state) { | |
| 114 BrowserThread::PostTask( | |
| 115 BrowserThread::IO, | |
| 116 FROM_HERE, | |
| 117 NewRunnableMethod(this, | |
| 118 &AudioRendererHost::DoRequestMoreData, | |
| 119 make_scoped_refptr(controller), | |
| 120 buffers_state)); | |
| 121 } | |
| 122 | |
| 123 void AudioRendererHost::DoCompleteCreation( | |
| 124 media::AudioOutputController* controller) { | |
| 125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 126 | |
| 127 AudioEntry* entry = LookupByController(controller); | |
| 128 if (!entry) | |
| 129 return; | |
| 130 | |
| 131 if (!peer_handle()) { | |
| 132 NOTREACHED() << "Renderer process handle is invalid."; | |
| 133 DeleteEntryOnError(entry); | |
| 134 return; | |
| 135 } | |
| 136 | |
| 137 // Once the audio stream is created then complete the creation process by | |
| 138 // mapping shared memory and sharing with the renderer process. | |
| 139 base::SharedMemoryHandle foreign_memory_handle; | |
| 140 if (!entry->shared_memory.ShareToProcess(peer_handle(), | |
| 141 &foreign_memory_handle)) { | |
| 142 // If we failed to map and share the shared memory then close the audio | |
| 143 // stream and send an error message. | |
| 144 DeleteEntryOnError(entry); | |
| 145 return; | |
| 146 } | |
| 147 | |
| 148 if (entry->controller->LowLatencyMode()) { | |
| 149 AudioSyncReader* reader = | |
| 150 static_cast<AudioSyncReader*>(entry->reader.get()); | |
| 151 | |
| 152 #if defined(OS_WIN) | |
| 153 base::SyncSocket::Handle foreign_socket_handle; | |
| 154 #else | |
| 155 base::FileDescriptor foreign_socket_handle; | |
| 156 #endif | |
| 157 | |
| 158 // If we failed to prepare the sync socket for the renderer then we fail | |
| 159 // the construction of audio stream. | |
| 160 if (!reader->PrepareForeignSocketHandle(peer_handle(), | |
| 161 &foreign_socket_handle)) { | |
| 162 DeleteEntryOnError(entry); | |
| 163 return; | |
| 164 } | |
| 165 | |
| 166 Send(new ViewMsg_NotifyLowLatencyAudioStreamCreated( | |
| 167 entry->render_view_id, entry->stream_id, foreign_memory_handle, | |
| 168 foreign_socket_handle, entry->shared_memory.created_size())); | |
| 169 return; | |
| 170 } | |
| 171 | |
| 172 // The normal audio stream has created, send a message to the renderer | |
| 173 // process. | |
| 174 Send(new ViewMsg_NotifyAudioStreamCreated( | |
| 175 entry->render_view_id, entry->stream_id, foreign_memory_handle, | |
| 176 entry->shared_memory.created_size())); | |
| 177 } | |
| 178 | |
| 179 void AudioRendererHost::DoSendPlayingMessage( | |
| 180 media::AudioOutputController* controller) { | |
| 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 182 | |
| 183 AudioEntry* entry = LookupByController(controller); | |
| 184 if (!entry) | |
| 185 return; | |
| 186 | |
| 187 ViewMsg_AudioStreamState_Params params; | |
| 188 params.state = ViewMsg_AudioStreamState_Params::kPlaying; | |
| 189 Send(new ViewMsg_NotifyAudioStreamStateChanged( | |
| 190 entry->render_view_id, entry->stream_id, params)); | |
| 191 } | |
| 192 | |
| 193 void AudioRendererHost::DoSendPausedMessage( | |
| 194 media::AudioOutputController* controller) { | |
| 195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 196 | |
| 197 AudioEntry* entry = LookupByController(controller); | |
| 198 if (!entry) | |
| 199 return; | |
| 200 | |
| 201 ViewMsg_AudioStreamState_Params params; | |
| 202 params.state = ViewMsg_AudioStreamState_Params::kPaused; | |
| 203 Send(new ViewMsg_NotifyAudioStreamStateChanged( | |
| 204 entry->render_view_id, entry->stream_id, params)); | |
| 205 } | |
| 206 | |
| 207 void AudioRendererHost::DoRequestMoreData( | |
| 208 media::AudioOutputController* controller, | |
| 209 AudioBuffersState buffers_state) { | |
| 210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 211 | |
| 212 // If we already have a pending request then return. | |
| 213 AudioEntry* entry = LookupByController(controller); | |
| 214 if (!entry || entry->pending_buffer_request) | |
| 215 return; | |
| 216 | |
| 217 DCHECK(!entry->controller->LowLatencyMode()); | |
| 218 entry->pending_buffer_request = true; | |
| 219 Send(new ViewMsg_RequestAudioPacket( | |
| 220 entry->render_view_id, entry->stream_id, buffers_state)); | |
| 221 } | |
| 222 | |
| 223 void AudioRendererHost::DoHandleError(media::AudioOutputController* controller, | |
| 224 int error_code) { | |
| 225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 226 | |
| 227 AudioEntry* entry = LookupByController(controller); | |
| 228 if (!entry) | |
| 229 return; | |
| 230 | |
| 231 DeleteEntryOnError(entry); | |
| 232 } | |
| 233 | |
| 234 /////////////////////////////////////////////////////////////////////////////// | |
| 235 // IPC Messages handler | |
| 236 bool AudioRendererHost::OnMessageReceived(const IPC::Message& message, | |
| 237 bool* message_was_ok) { | |
| 238 bool handled = true; | |
| 239 IPC_BEGIN_MESSAGE_MAP_EX(AudioRendererHost, message, *message_was_ok) | |
| 240 IPC_MESSAGE_HANDLER(ViewHostMsg_CreateAudioStream, OnCreateStream) | |
| 241 IPC_MESSAGE_HANDLER(ViewHostMsg_PlayAudioStream, OnPlayStream) | |
| 242 IPC_MESSAGE_HANDLER(ViewHostMsg_PauseAudioStream, OnPauseStream) | |
| 243 IPC_MESSAGE_HANDLER(ViewHostMsg_FlushAudioStream, OnFlushStream) | |
| 244 IPC_MESSAGE_HANDLER(ViewHostMsg_CloseAudioStream, OnCloseStream) | |
| 245 IPC_MESSAGE_HANDLER(ViewHostMsg_NotifyAudioPacketReady, OnNotifyPacketReady) | |
| 246 IPC_MESSAGE_HANDLER(ViewHostMsg_GetAudioVolume, OnGetVolume) | |
| 247 IPC_MESSAGE_HANDLER(ViewHostMsg_SetAudioVolume, OnSetVolume) | |
| 248 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 249 IPC_END_MESSAGE_MAP_EX() | |
| 250 | |
| 251 return handled; | |
| 252 } | |
| 253 | |
| 254 void AudioRendererHost::OnCreateStream( | |
| 255 const IPC::Message& msg, int stream_id, | |
| 256 const ViewHostMsg_Audio_CreateStream_Params& params, bool low_latency) { | |
| 257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 258 DCHECK(LookupById(msg.routing_id(), stream_id) == NULL); | |
| 259 | |
| 260 AudioParameters audio_params(params.params); | |
| 261 | |
| 262 // Select the hardware packet size if not specified. | |
| 263 if (!audio_params.samples_per_packet) { | |
| 264 audio_params.samples_per_packet = SelectSamplesPerPacket(audio_params); | |
| 265 } | |
| 266 uint32 packet_size = audio_params.GetPacketSize(); | |
| 267 | |
| 268 scoped_ptr<AudioEntry> entry(new AudioEntry()); | |
| 269 // Create the shared memory and share with the renderer process. | |
| 270 if (!entry->shared_memory.CreateAndMapAnonymous(packet_size)) { | |
| 271 // If creation of shared memory failed then send an error message. | |
| 272 SendErrorMessage(msg.routing_id(), stream_id); | |
| 273 return; | |
| 274 } | |
| 275 | |
| 276 if (low_latency) { | |
| 277 // If this is the low latency mode, we need to construct a SyncReader first. | |
| 278 scoped_ptr<AudioSyncReader> reader( | |
| 279 new AudioSyncReader(&entry->shared_memory)); | |
| 280 | |
| 281 // Then try to initialize the sync reader. | |
| 282 if (!reader->Init()) { | |
| 283 SendErrorMessage(msg.routing_id(), stream_id); | |
| 284 return; | |
| 285 } | |
| 286 | |
| 287 // If we have successfully created the SyncReader then assign it to the | |
| 288 // entry and construct an AudioOutputController. | |
| 289 entry->reader.reset(reader.release()); | |
| 290 entry->controller = | |
| 291 media::AudioOutputController::CreateLowLatency(this, audio_params, | |
| 292 entry->reader.get()); | |
| 293 } else { | |
| 294 // The choice of buffer capacity is based on experiment. | |
| 295 entry->controller = | |
| 296 media::AudioOutputController::Create(this, audio_params, | |
| 297 3 * packet_size); | |
| 298 } | |
| 299 | |
| 300 if (!entry->controller) { | |
| 301 SendErrorMessage(msg.routing_id(), stream_id); | |
| 302 return; | |
| 303 } | |
| 304 | |
| 305 // If we have created the controller successfully create a entry and add it | |
| 306 // to the map. | |
| 307 entry->render_view_id = msg.routing_id(); | |
| 308 entry->stream_id = stream_id; | |
| 309 | |
| 310 audio_entries_.insert(std::make_pair( | |
| 311 AudioEntryId(msg.routing_id(), stream_id), | |
| 312 entry.release())); | |
| 313 } | |
| 314 | |
| 315 void AudioRendererHost::OnPlayStream(const IPC::Message& msg, int stream_id) { | |
| 316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 317 | |
| 318 AudioEntry* entry = LookupById(msg.routing_id(), stream_id); | |
| 319 if (!entry) { | |
| 320 SendErrorMessage(msg.routing_id(), stream_id); | |
| 321 return; | |
| 322 } | |
| 323 | |
| 324 entry->controller->Play(); | |
| 325 } | |
| 326 | |
| 327 void AudioRendererHost::OnPauseStream(const IPC::Message& msg, int stream_id) { | |
| 328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 329 | |
| 330 AudioEntry* entry = LookupById(msg.routing_id(), stream_id); | |
| 331 if (!entry) { | |
| 332 SendErrorMessage(msg.routing_id(), stream_id); | |
| 333 return; | |
| 334 } | |
| 335 | |
| 336 entry->controller->Pause(); | |
| 337 } | |
| 338 | |
| 339 void AudioRendererHost::OnFlushStream(const IPC::Message& msg, int stream_id) { | |
| 340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 341 | |
| 342 AudioEntry* entry = LookupById(msg.routing_id(), stream_id); | |
| 343 if (!entry) { | |
| 344 SendErrorMessage(msg.routing_id(), stream_id); | |
| 345 return; | |
| 346 } | |
| 347 | |
| 348 entry->controller->Flush(); | |
| 349 } | |
| 350 | |
| 351 void AudioRendererHost::OnCloseStream(const IPC::Message& msg, int stream_id) { | |
| 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 353 | |
| 354 AudioEntry* entry = LookupById(msg.routing_id(), stream_id); | |
| 355 | |
| 356 if (entry) | |
| 357 CloseAndDeleteStream(entry); | |
| 358 } | |
| 359 | |
| 360 void AudioRendererHost::OnSetVolume(const IPC::Message& msg, int stream_id, | |
| 361 double volume) { | |
| 362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 363 | |
| 364 AudioEntry* entry = LookupById(msg.routing_id(), stream_id); | |
| 365 if (!entry) { | |
| 366 SendErrorMessage(msg.routing_id(), stream_id); | |
| 367 return; | |
| 368 } | |
| 369 | |
| 370 // Make sure the volume is valid. | |
| 371 if (volume < 0 || volume > 1.0) | |
| 372 return; | |
| 373 entry->controller->SetVolume(volume); | |
| 374 } | |
| 375 | |
| 376 void AudioRendererHost::OnGetVolume(const IPC::Message& msg, int stream_id) { | |
| 377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 378 NOTREACHED() << "This message shouldn't be received"; | |
| 379 } | |
| 380 | |
| 381 void AudioRendererHost::OnNotifyPacketReady( | |
| 382 const IPC::Message& msg, int stream_id, uint32 packet_size) { | |
| 383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 384 | |
| 385 AudioEntry* entry = LookupById(msg.routing_id(), stream_id); | |
| 386 if (!entry) { | |
| 387 SendErrorMessage(msg.routing_id(), stream_id); | |
| 388 return; | |
| 389 } | |
| 390 | |
| 391 DCHECK(!entry->controller->LowLatencyMode()); | |
| 392 CHECK(packet_size <= entry->shared_memory.created_size()); | |
| 393 | |
| 394 if (!entry->pending_buffer_request) { | |
| 395 NOTREACHED() << "Buffer received but no such pending request"; | |
| 396 } | |
| 397 entry->pending_buffer_request = false; | |
| 398 | |
| 399 // Enqueue the data to media::AudioOutputController. | |
| 400 entry->controller->EnqueueData( | |
| 401 reinterpret_cast<uint8*>(entry->shared_memory.memory()), | |
| 402 packet_size); | |
| 403 } | |
| 404 | |
| 405 void AudioRendererHost::SendErrorMessage(int32 render_view_id, | |
| 406 int32 stream_id) { | |
| 407 ViewMsg_AudioStreamState_Params state; | |
| 408 state.state = ViewMsg_AudioStreamState_Params::kError; | |
| 409 Send(new ViewMsg_NotifyAudioStreamStateChanged( | |
| 410 render_view_id, stream_id, state)); | |
| 411 } | |
| 412 | |
| 413 void AudioRendererHost::DeleteEntries() { | |
| 414 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 415 | |
| 416 for (AudioEntryMap::iterator i = audio_entries_.begin(); | |
| 417 i != audio_entries_.end(); ++i) { | |
| 418 CloseAndDeleteStream(i->second); | |
| 419 } | |
| 420 } | |
| 421 | |
| 422 void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) { | |
| 423 if (!entry->pending_close) { | |
| 424 entry->controller->Close( | |
| 425 NewRunnableMethod(this, &AudioRendererHost::OnStreamClosed, entry)); | |
| 426 entry->pending_close = true; | |
| 427 } | |
| 428 } | |
| 429 | |
| 430 void AudioRendererHost::OnStreamClosed(AudioEntry* entry) { | |
| 431 // Delete the entry after we've closed the stream. | |
| 432 BrowserThread::PostTask( | |
| 433 BrowserThread::IO, FROM_HERE, | |
| 434 NewRunnableMethod(this, &AudioRendererHost::DeleteEntry, entry)); | |
| 435 } | |
| 436 | |
| 437 void AudioRendererHost::DeleteEntry(AudioEntry* entry) { | |
| 438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 439 | |
| 440 // Delete the entry when this method goes out of scope. | |
| 441 scoped_ptr<AudioEntry> entry_deleter(entry); | |
| 442 | |
| 443 // Erase the entry from the map. | |
| 444 audio_entries_.erase( | |
| 445 AudioEntryId(entry->render_view_id, entry->stream_id)); | |
| 446 } | |
| 447 | |
| 448 void AudioRendererHost::DeleteEntryOnError(AudioEntry* entry) { | |
| 449 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 450 | |
| 451 // Sends the error message first before we close the stream because | |
| 452 // |entry| is destroyed in DeleteEntry(). | |
| 453 SendErrorMessage(entry->render_view_id, entry->stream_id); | |
| 454 CloseAndDeleteStream(entry); | |
| 455 } | |
| 456 | |
| 457 AudioRendererHost::AudioEntry* AudioRendererHost::LookupById( | |
| 458 int route_id, int stream_id) { | |
| 459 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 460 | |
| 461 AudioEntryMap::iterator i = audio_entries_.find( | |
| 462 AudioEntryId(route_id, stream_id)); | |
| 463 if (i != audio_entries_.end() && !i->second->pending_close) | |
| 464 return i->second; | |
| 465 return NULL; | |
| 466 } | |
| 467 | |
| 468 AudioRendererHost::AudioEntry* AudioRendererHost::LookupByController( | |
| 469 media::AudioOutputController* controller) { | |
| 470 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 471 | |
| 472 // Iterate the map of entries. | |
| 473 // TODO(hclam): Implement a faster look up method. | |
| 474 for (AudioEntryMap::iterator i = audio_entries_.begin(); | |
| 475 i != audio_entries_.end(); ++i) { | |
| 476 if (!i->second->pending_close && controller == i->second->controller.get()) | |
| 477 return i->second; | |
| 478 } | |
| 479 return NULL; | |
| 480 } | |
| OLD | NEW |