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/win/audio_low_latency_output_win.h" | 5 #include "media/audio/win/audio_low_latency_output_win.h" |
6 | 6 |
7 #include <Functiondiscoverykeys_devpkey.h> | 7 #include <Functiondiscoverykeys_devpkey.h> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/memory/scoped_ptr.h" | 10 #include "base/memory/scoped_ptr.h" |
11 #include "base/utf_string_conversions.h" | 11 #include "base/utf_string_conversions.h" |
12 #include "media/audio/audio_util.h" | 12 #include "media/audio/audio_util.h" |
13 #include "media/audio/win/audio_manager_win.h" | 13 #include "media/audio/win/audio_manager_win.h" |
14 #include "media/audio/win/avrt_wrapper_win.h" | 14 #include "media/audio/win/avrt_wrapper_win.h" |
15 | 15 |
16 using base::win::ScopedComPtr; | 16 using base::win::ScopedComPtr; |
17 using base::win::ScopedCOMInitializer; | 17 using base::win::ScopedCOMInitializer; |
18 | 18 |
19 namespace media { | 19 namespace media { |
20 | 20 |
21 WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, | 21 WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
22 const AudioParameters& params, | 22 const AudioParameters& params, |
23 ERole device_role) | 23 ERole device_role, |
| 24 AUDCLNT_SHAREMODE share_mode) |
24 : com_init_(ScopedCOMInitializer::kMTA), | 25 : com_init_(ScopedCOMInitializer::kMTA), |
25 creating_thread_id_(base::PlatformThread::CurrentId()), | 26 creating_thread_id_(base::PlatformThread::CurrentId()), |
26 manager_(manager), | 27 manager_(manager), |
27 render_thread_(NULL), | 28 render_thread_(NULL), |
28 opened_(false), | 29 opened_(false), |
29 started_(false), | 30 started_(false), |
30 restart_rendering_mode_(false), | 31 restart_rendering_mode_(false), |
31 volume_(1.0), | 32 volume_(1.0), |
32 endpoint_buffer_size_frames_(0), | 33 endpoint_buffer_size_frames_(0), |
33 device_role_(device_role), | 34 device_role_(device_role), |
| 35 share_mode_(share_mode), |
34 num_written_frames_(0), | 36 num_written_frames_(0), |
35 source_(NULL) { | 37 source_(NULL) { |
36 CHECK(com_init_.succeeded()); | 38 CHECK(com_init_.succeeded()); |
37 DCHECK(manager_); | 39 DCHECK(manager_); |
38 | 40 |
39 // Load the Avrt DLL if not already loaded. Required to support MMCSS. | 41 // Load the Avrt DLL if not already loaded. Required to support MMCSS. |
40 bool avrt_init = avrt::Initialize(); | 42 bool avrt_init = avrt::Initialize(); |
41 DCHECK(avrt_init) << "Failed to load the avrt.dll"; | 43 DCHECK(avrt_init) << "Failed to load the avrt.dll"; |
42 | 44 |
| 45 if (share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE) { |
| 46 DVLOG(1) << ">> Note that EXCLUSIVE MODE is enabled <<"; |
| 47 } |
| 48 |
43 // Set up the desired render format specified by the client. | 49 // Set up the desired render format specified by the client. |
44 format_.nSamplesPerSec = params.sample_rate(); | 50 format_.nSamplesPerSec = params.sample_rate(); |
45 format_.wFormatTag = WAVE_FORMAT_PCM; | 51 format_.wFormatTag = WAVE_FORMAT_PCM; |
46 format_.wBitsPerSample = params.bits_per_sample(); | 52 format_.wBitsPerSample = params.bits_per_sample(); |
47 format_.nChannels = params.channels(); | 53 format_.nChannels = params.channels(); |
48 format_.nBlockAlign = (format_.wBitsPerSample / 8) * format_.nChannels; | 54 format_.nBlockAlign = (format_.wBitsPerSample / 8) * format_.nChannels; |
49 format_.nAvgBytesPerSec = format_.nSamplesPerSec * format_.nBlockAlign; | 55 format_.nAvgBytesPerSec = format_.nSamplesPerSec * format_.nBlockAlign; |
50 format_.cbSize = 0; | 56 format_.cbSize = 0; |
51 | 57 |
52 // Size in bytes of each audio frame. | 58 // Size in bytes of each audio frame. |
(...skipping 27 matching lines...) Expand all Loading... |
80 WASAPIAudioOutputStream::~WASAPIAudioOutputStream() {} | 86 WASAPIAudioOutputStream::~WASAPIAudioOutputStream() {} |
81 | 87 |
82 bool WASAPIAudioOutputStream::Open() { | 88 bool WASAPIAudioOutputStream::Open() { |
83 DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); | 89 DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); |
84 if (opened_) | 90 if (opened_) |
85 return true; | 91 return true; |
86 | 92 |
87 // Create an IMMDeviceEnumerator interface and obtain a reference to | 93 // Create an IMMDeviceEnumerator interface and obtain a reference to |
88 // the IMMDevice interface of the default rendering device with the | 94 // the IMMDevice interface of the default rendering device with the |
89 // specified role. | 95 // specified role. |
90 HRESULT hr = SetRenderDevice(device_role_); | 96 HRESULT hr = SetRenderDevice(); |
91 if (FAILED(hr)) { | 97 if (FAILED(hr)) { |
92 return false; | 98 return false; |
93 } | 99 } |
94 | 100 |
95 // Obtain an IAudioClient interface which enables us to create and initialize | 101 // Obtain an IAudioClient interface which enables us to create and initialize |
96 // an audio stream between an audio application and the audio engine. | 102 // an audio stream between an audio application and the audio engine. |
97 hr = ActivateRenderDevice(); | 103 hr = ActivateRenderDevice(); |
98 if (FAILED(hr)) { | 104 if (FAILED(hr)) { |
99 return false; | 105 return false; |
100 } | 106 } |
101 | 107 |
102 // Retrieve the stream format which the audio engine uses for its internal | 108 // Retrieve the stream format which the audio engine uses for its internal |
103 // processing/mixing of shared-mode streams. | 109 // processing/mixing of shared-mode streams. The result of this method is |
| 110 // ignored for shared mode streams. |
104 hr = GetAudioEngineStreamFormat(); | 111 hr = GetAudioEngineStreamFormat(); |
105 if (FAILED(hr)) { | 112 if (FAILED(hr)) { |
106 return false; | 113 return false; |
107 } | 114 } |
108 | 115 |
109 // Verify that the selected audio endpoint supports the specified format | 116 // Verify that the selected audio endpoint supports the specified format |
110 // set during construction. | 117 // set during construction. |
| 118 // In exclusive mode, the client can choose to open the stream in any audio |
| 119 // format that the endpoint device supports. In shared mode, the client must |
| 120 // open the stream in the mix format that is currently in use by the audio |
| 121 // engine (or a format that is similar to the mix format). The audio engine's |
| 122 // input streams and the output mix from the engine are all in this format. |
111 if (!DesiredFormatIsSupported()) { | 123 if (!DesiredFormatIsSupported()) { |
112 return false; | 124 return false; |
113 } | 125 } |
114 | 126 |
115 // Initialize the audio stream between the client and the device using | 127 // Initialize the audio stream between the client and the device using |
116 // shared mode and a lowest possible glitch-free latency. | 128 // shared or exclusive mode and a lowest possible glitch-free latency. |
| 129 // We will enter different code paths depending on the specified share mode. |
117 hr = InitializeAudioEngine(); | 130 hr = InitializeAudioEngine(); |
118 if (FAILED(hr)) { | 131 if (FAILED(hr)) { |
119 return false; | 132 return false; |
120 } | 133 } |
121 | 134 |
122 // Register this client as an IMMNotificationClient implementation. | 135 // Register this client as an IMMNotificationClient implementation. |
123 // Only OnDefaultDeviceChanged() and OnDeviceStateChanged() and are | 136 // Only OnDefaultDeviceChanged() and OnDeviceStateChanged() and are |
124 // non-trivial. | 137 // non-trivial. |
125 hr = device_enumerator_->RegisterEndpointNotificationCallback(this); | 138 hr = device_enumerator_->RegisterEndpointNotificationCallback(this); |
126 | 139 |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
222 // Flush all pending data and reset the audio clock stream position to 0. | 235 // Flush all pending data and reset the audio clock stream position to 0. |
223 hr = audio_client_->Reset(); | 236 hr = audio_client_->Reset(); |
224 if (FAILED(hr)) { | 237 if (FAILED(hr)) { |
225 DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED) | 238 DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED) |
226 << "Failed to reset streaming: " << std::hex << hr; | 239 << "Failed to reset streaming: " << std::hex << hr; |
227 } | 240 } |
228 | 241 |
229 // Extra safety check to ensure that the buffers are cleared. | 242 // Extra safety check to ensure that the buffers are cleared. |
230 // If the buffers are not cleared correctly, the next call to Start() | 243 // If the buffers are not cleared correctly, the next call to Start() |
231 // would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer(). | 244 // would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer(). |
232 UINT32 num_queued_frames = 0; | 245 // This check is is only needed for shared-mode streams. |
233 audio_client_->GetCurrentPadding(&num_queued_frames); | 246 if (share_mode() == AUDCLNT_SHAREMODE_SHARED) { |
234 DCHECK_EQ(0u, num_queued_frames); | 247 UINT32 num_queued_frames = 0; |
| 248 audio_client_->GetCurrentPadding(&num_queued_frames); |
| 249 DCHECK_EQ(0u, num_queued_frames); |
| 250 } |
235 | 251 |
236 // Ensure that we don't quit the main thread loop immediately next | 252 // Ensure that we don't quit the main thread loop immediately next |
237 // time Start() is called. | 253 // time Start() is called. |
238 ResetEvent(stop_render_event_.Get()); | 254 ResetEvent(stop_render_event_.Get()); |
239 | 255 |
240 started_ = false; | 256 started_ = false; |
241 } | 257 } |
242 | 258 |
243 void WASAPIAudioOutputStream::Close() { | 259 void WASAPIAudioOutputStream::Close() { |
244 DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); | 260 DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); |
(...skipping 26 matching lines...) Expand all Loading... |
271 | 287 |
272 void WASAPIAudioOutputStream::GetVolume(double* volume) { | 288 void WASAPIAudioOutputStream::GetVolume(double* volume) { |
273 DVLOG(1) << "GetVolume()"; | 289 DVLOG(1) << "GetVolume()"; |
274 *volume = static_cast<double>(volume_); | 290 *volume = static_cast<double>(volume_); |
275 } | 291 } |
276 | 292 |
277 // static | 293 // static |
278 int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) { | 294 int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) { |
279 // It is assumed that this static method is called from a COM thread, i.e., | 295 // It is assumed that this static method is called from a COM thread, i.e., |
280 // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. | 296 // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. |
| 297 // Note that, calling this function only makes sense for shared mode streams, |
| 298 // since if the device will be opened in exclusive mode, then the application |
| 299 // specified format is used instead. |
281 ScopedComPtr<IMMDeviceEnumerator> enumerator; | 300 ScopedComPtr<IMMDeviceEnumerator> enumerator; |
282 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), | 301 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
283 NULL, | 302 NULL, |
284 CLSCTX_INPROC_SERVER, | 303 CLSCTX_INPROC_SERVER, |
285 __uuidof(IMMDeviceEnumerator), | 304 __uuidof(IMMDeviceEnumerator), |
286 enumerator.ReceiveVoid()); | 305 enumerator.ReceiveVoid()); |
287 if (FAILED(hr)) { | 306 if (FAILED(hr)) { |
288 NOTREACHED() << "error code: " << std::hex << hr; | 307 NOTREACHED() << "error code: " << std::hex << hr; |
289 return 0.0; | 308 return 0.0; |
290 } | 309 } |
(...skipping 13 matching lines...) Expand all Loading... |
304 ScopedComPtr<IAudioClient> audio_client; | 323 ScopedComPtr<IAudioClient> audio_client; |
305 hr = endpoint_device->Activate(__uuidof(IAudioClient), | 324 hr = endpoint_device->Activate(__uuidof(IAudioClient), |
306 CLSCTX_INPROC_SERVER, | 325 CLSCTX_INPROC_SERVER, |
307 NULL, | 326 NULL, |
308 audio_client.ReceiveVoid()); | 327 audio_client.ReceiveVoid()); |
309 if (FAILED(hr)) { | 328 if (FAILED(hr)) { |
310 NOTREACHED() << "error code: " << std::hex << hr; | 329 NOTREACHED() << "error code: " << std::hex << hr; |
311 return 0.0; | 330 return 0.0; |
312 } | 331 } |
313 | 332 |
| 333 // Retrieve the stream format that the audio engine uses for its internal |
| 334 // processing of shared-mode streams. |
314 base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; | 335 base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; |
315 hr = audio_client->GetMixFormat(&audio_engine_mix_format); | 336 hr = audio_client->GetMixFormat(&audio_engine_mix_format); |
316 if (FAILED(hr)) { | 337 if (FAILED(hr)) { |
317 NOTREACHED() << "error code: " << std::hex << hr; | 338 NOTREACHED() << "error code: " << std::hex << hr; |
318 return 0.0; | 339 return 0.0; |
319 } | 340 } |
320 | 341 |
321 return static_cast<int>(audio_engine_mix_format->nSamplesPerSec); | 342 return static_cast<int>(audio_engine_mix_format->nSamplesPerSec); |
322 } | 343 } |
323 | 344 |
(...skipping 14 matching lines...) Expand all Loading... |
338 // Failed to enable MMCSS on this thread. It is not fatal but can lead | 359 // Failed to enable MMCSS on this thread. It is not fatal but can lead |
339 // to reduced QoS at high load. | 360 // to reduced QoS at high load. |
340 DWORD err = GetLastError(); | 361 DWORD err = GetLastError(); |
341 LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ")."; | 362 LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ")."; |
342 } | 363 } |
343 | 364 |
344 HRESULT hr = S_FALSE; | 365 HRESULT hr = S_FALSE; |
345 | 366 |
346 bool playing = true; | 367 bool playing = true; |
347 bool error = false; | 368 bool error = false; |
348 HANDLE wait_array[] = { stop_render_event_, | 369 HANDLE wait_array[] = {stop_render_event_, |
349 stream_switch_event_, | 370 stream_switch_event_, |
350 audio_samples_render_event_ }; | 371 audio_samples_render_event_ }; |
351 UINT64 device_frequency = 0; | 372 UINT64 device_frequency = 0; |
352 | 373 |
353 // The IAudioClock interface enables us to monitor a stream's data | 374 // The IAudioClock interface enables us to monitor a stream's data |
354 // rate and the current position in the stream. Allocate it before we | 375 // rate and the current position in the stream. Allocate it before we |
355 // start spinning. | 376 // start spinning. |
356 ScopedComPtr<IAudioClock> audio_clock; | 377 ScopedComPtr<IAudioClock> audio_clock; |
357 hr = audio_client_->GetService(__uuidof(IAudioClock), | 378 hr = audio_client_->GetService(__uuidof(IAudioClock), |
358 audio_clock.ReceiveVoid()); | 379 audio_clock.ReceiveVoid()); |
359 if (SUCCEEDED(hr)) { | 380 if (SUCCEEDED(hr)) { |
360 // The device frequency is the frequency generated by the hardware clock in | 381 // The device frequency is the frequency generated by the hardware clock in |
(...skipping 26 matching lines...) Expand all Loading... |
387 playing = false; | 408 playing = false; |
388 error = true; | 409 error = true; |
389 } | 410 } |
390 break; | 411 break; |
391 case WAIT_OBJECT_0 + 2: | 412 case WAIT_OBJECT_0 + 2: |
392 { | 413 { |
393 // |audio_samples_render_event_| has been set. | 414 // |audio_samples_render_event_| has been set. |
394 UINT32 num_queued_frames = 0; | 415 UINT32 num_queued_frames = 0; |
395 uint8* audio_data = NULL; | 416 uint8* audio_data = NULL; |
396 | 417 |
397 // Get the padding value which represents the amount of rendering | 418 // Contains how much new data we can write to the buffer without |
398 // data that is queued up to play in the endpoint buffer. | |
399 hr = audio_client_->GetCurrentPadding(&num_queued_frames); | |
400 | |
401 // Determine how much new data we can write to the buffer without | |
402 // the risk of overwriting previously written data that the audio | 419 // the risk of overwriting previously written data that the audio |
403 // engine has not yet read from the buffer. | 420 // engine has not yet read from the buffer. |
404 size_t num_available_frames = | 421 size_t num_available_frames = 0; |
405 endpoint_buffer_size_frames_ - num_queued_frames; | 422 |
| 423 if (share_mode() == AUDCLNT_SHAREMODE_SHARED) { |
| 424 // Get the padding value which represents the amount of rendering |
| 425 // data that is queued up to play in the endpoint buffer. |
| 426 hr = audio_client_->GetCurrentPadding(&num_queued_frames); |
| 427 num_available_frames = |
| 428 endpoint_buffer_size_frames_ - num_queued_frames; |
| 429 } else { |
| 430 // While the stream is running, the system alternately sends one |
| 431 // buffer or the other to the client. This form of double buffering |
| 432 // is referred to as "ping-ponging". Each time the client receives |
| 433 // a buffer from the system (triggers this event) the client must |
| 434 // process the entire buffer. Calls to the GetCurrentPadding method |
| 435 // are unnecessary because the packet size must always equal the |
| 436 // buffer size. In contrast to the shared mode buffering scheme, |
| 437 // the latency for an event-driven, exclusive-mode stream depends |
| 438 // directly on the buffer size. |
| 439 num_available_frames = endpoint_buffer_size_frames_; |
| 440 } |
406 | 441 |
407 // Check if there is enough available space to fit the packet size | 442 // Check if there is enough available space to fit the packet size |
408 // specified by the client. | 443 // specified by the client. |
409 if (FAILED(hr) || (num_available_frames < packet_size_frames_)) | 444 if (FAILED(hr) || (num_available_frames < packet_size_frames_)) |
410 continue; | 445 continue; |
411 | 446 |
412 // Derive the number of packets we need get from the client to | 447 // Derive the number of packets we need get from the client to |
413 // fill up the available area in the endpoint buffer. | 448 // fill up the available area in the endpoint buffer. |
| 449 // |num_packets| will always be one for exclusive-mode streams. |
414 size_t num_packets = (num_available_frames / packet_size_frames_); | 450 size_t num_packets = (num_available_frames / packet_size_frames_); |
415 | 451 |
416 // Get data from the client/source. | 452 // Get data from the client/source. |
417 for (size_t n = 0; n < num_packets; ++n) { | 453 for (size_t n = 0; n < num_packets; ++n) { |
418 // Grab all available space in the rendering endpoint buffer | 454 // Grab all available space in the rendering endpoint buffer |
419 // into which the client can write a data packet. | 455 // into which the client can write a data packet. |
420 hr = audio_render_client_->GetBuffer(packet_size_frames_, | 456 hr = audio_render_client_->GetBuffer(packet_size_frames_, |
421 &audio_data); | 457 &audio_data); |
422 if (FAILED(hr)) { | 458 if (FAILED(hr)) { |
423 DLOG(ERROR) << "Failed to use rendering audio buffer: " | 459 DLOG(ERROR) << "Failed to use rendering audio buffer: " |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
504 PLOG(WARNING) << "Failed to disable MMCSS"; | 540 PLOG(WARNING) << "Failed to disable MMCSS"; |
505 } | 541 } |
506 } | 542 } |
507 | 543 |
508 void WASAPIAudioOutputStream::HandleError(HRESULT err) { | 544 void WASAPIAudioOutputStream::HandleError(HRESULT err) { |
509 NOTREACHED() << "Error code: " << std::hex << err; | 545 NOTREACHED() << "Error code: " << std::hex << err; |
510 if (source_) | 546 if (source_) |
511 source_->OnError(this, static_cast<int>(err)); | 547 source_->OnError(this, static_cast<int>(err)); |
512 } | 548 } |
513 | 549 |
514 HRESULT WASAPIAudioOutputStream::SetRenderDevice(ERole device_role) { | 550 HRESULT WASAPIAudioOutputStream::SetRenderDevice() { |
515 // Create the IMMDeviceEnumerator interface. | 551 // Create the IMMDeviceEnumerator interface. |
516 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), | 552 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
517 NULL, | 553 NULL, |
518 CLSCTX_INPROC_SERVER, | 554 CLSCTX_INPROC_SERVER, |
519 __uuidof(IMMDeviceEnumerator), | 555 __uuidof(IMMDeviceEnumerator), |
520 device_enumerator_.ReceiveVoid()); | 556 device_enumerator_.ReceiveVoid()); |
521 if (SUCCEEDED(hr)) { | 557 if (SUCCEEDED(hr)) { |
522 // Retrieve the default render audio endpoint for the specified role. | 558 // Retrieve the default render audio endpoint for the specified role. |
523 // Note that, in Windows Vista, the MMDevice API supports device roles | 559 // Note that, in Windows Vista, the MMDevice API supports device roles |
524 // but the system-supplied user interface programs do not. | 560 // but the system-supplied user interface programs do not. |
525 hr = device_enumerator_->GetDefaultAudioEndpoint( | 561 hr = device_enumerator_->GetDefaultAudioEndpoint( |
526 eRender, device_role, endpoint_device_.Receive()); | 562 eRender, device_role_, endpoint_device_.Receive()); |
527 if (FAILED(hr)) | 563 if (FAILED(hr)) |
528 return hr; | 564 return hr; |
529 | 565 |
530 // Verify that the audio endpoint device is active. That is, the audio | 566 // Verify that the audio endpoint device is active. That is, the audio |
531 // adapter that connects to the endpoint device is present and enabled. | 567 // adapter that connects to the endpoint device is present and enabled. |
532 DWORD state = DEVICE_STATE_DISABLED; | 568 DWORD state = DEVICE_STATE_DISABLED; |
533 hr = endpoint_device_->GetState(&state); | 569 hr = endpoint_device_->GetState(&state); |
534 if (SUCCEEDED(hr)) { | 570 if (SUCCEEDED(hr)) { |
535 if (!(state & DEVICE_STATE_ACTIVE)) { | 571 if (!(state & DEVICE_STATE_ACTIVE)) { |
536 DLOG(ERROR) << "Selected render device is not active."; | 572 DLOG(ERROR) << "Selected render device is not active."; |
(...skipping 15 matching lines...) Expand all Loading... |
552 return hr; | 588 return hr; |
553 } | 589 } |
554 | 590 |
555 HRESULT WASAPIAudioOutputStream::GetAudioEngineStreamFormat() { | 591 HRESULT WASAPIAudioOutputStream::GetAudioEngineStreamFormat() { |
556 // Retrieve the stream format that the audio engine uses for its internal | 592 // Retrieve the stream format that the audio engine uses for its internal |
557 // processing/mixing of shared-mode streams. | 593 // processing/mixing of shared-mode streams. |
558 return audio_client_->GetMixFormat(&audio_engine_mix_format_); | 594 return audio_client_->GetMixFormat(&audio_engine_mix_format_); |
559 } | 595 } |
560 | 596 |
561 bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { | 597 bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { |
| 598 // Determine, before calling IAudioClient::Initialize, whether the audio |
| 599 // engine supports a particular stream format. |
562 // In shared mode, the audio engine always supports the mix format, | 600 // In shared mode, the audio engine always supports the mix format, |
563 // which is stored in the |audio_engine_mix_format_| member. In addition, | 601 // which is stored in the |audio_engine_mix_format_| member. |
564 // the audio engine *might* support similar formats that have the same | |
565 // sample rate and number of channels as the mix format but differ in | |
566 // the representation of audio sample values. | |
567 base::win::ScopedCoMem<WAVEFORMATEX> closest_match; | 602 base::win::ScopedCoMem<WAVEFORMATEX> closest_match; |
568 HRESULT hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, | 603 HRESULT hr = audio_client_->IsFormatSupported(share_mode_, |
569 &format_, | 604 &format_, |
570 &closest_match); | 605 &closest_match); |
571 DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported " | 606 DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported " |
572 << "but a closest match exists."; | 607 << "but a closest match exists."; |
573 return (hr == S_OK); | 608 return (hr == S_OK); |
574 } | 609 } |
575 | 610 |
576 HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { | 611 HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { |
577 // TODO(henrika): this buffer scheme is still under development. | |
578 // The exact details are yet to be determined based on tests with different | |
579 // audio clients. | |
580 int glitch_free_buffer_size_ms = static_cast<int>(packet_size_ms_ + 0.5); | |
581 if (audio_engine_mix_format_->nSamplesPerSec == 48000) { | |
582 // Initial tests have shown that we have to add 10 ms extra to | |
583 // ensure that we don't run empty for any packet size. | |
584 glitch_free_buffer_size_ms += 10; | |
585 } else if (audio_engine_mix_format_->nSamplesPerSec == 44100) { | |
586 // Initial tests have shown that we have to add 20 ms extra to | |
587 // ensure that we don't run empty for any packet size. | |
588 glitch_free_buffer_size_ms += 20; | |
589 } else { | |
590 glitch_free_buffer_size_ms += 20; | |
591 } | |
592 DVLOG(1) << "glitch_free_buffer_size_ms: " << glitch_free_buffer_size_ms; | |
593 REFERENCE_TIME requested_buffer_duration_hns = | |
594 static_cast<REFERENCE_TIME>(glitch_free_buffer_size_ms * 10000); | |
595 | |
596 // Initialize the audio stream between the client and the device. | |
597 // We connect indirectly through the audio engine by using shared mode | |
598 // and WASAPI is initialized in an event driven mode. | |
599 // Note that this API ensures that the buffer is never smaller than the | |
600 // minimum buffer size needed to ensure glitch-free rendering. | |
601 // If we requests a buffer size that is smaller than the audio engine's | |
602 // minimum required buffer size, the method sets the buffer size to this | |
603 // minimum buffer size rather than to the buffer size requested. | |
604 HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, | |
605 AUDCLNT_STREAMFLAGS_EVENTCALLBACK | | |
606 AUDCLNT_STREAMFLAGS_NOPERSIST, | |
607 requested_buffer_duration_hns, | |
608 0, | |
609 &format_, | |
610 NULL); | |
611 if (FAILED(hr)) | |
612 return hr; | |
613 | |
614 // Retrieve the length of the endpoint buffer shared between the client | |
615 // and the audio engine. The buffer length the buffer length determines | |
616 // the maximum amount of rendering data that the client can write to | |
617 // the endpoint buffer during a single processing pass. | |
618 // A typical value is 960 audio frames <=> 20ms @ 48kHz sample rate. | |
619 hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_); | |
620 if (FAILED(hr)) | |
621 return hr; | |
622 DVLOG(1) << "endpoint buffer size: " << endpoint_buffer_size_frames_ | |
623 << " [frames]"; | |
624 #ifndef NDEBUG | 612 #ifndef NDEBUG |
625 // The period between processing passes by the audio engine is fixed for a | 613 // The period between processing passes by the audio engine is fixed for a |
626 // particular audio endpoint device and represents the smallest processing | 614 // particular audio endpoint device and represents the smallest processing |
627 // quantum for the audio engine. This period plus the stream latency between | 615 // quantum for the audio engine. This period plus the stream latency between |
628 // the buffer and endpoint device represents the minimum possible latency | 616 // the buffer and endpoint device represents the minimum possible latency |
629 // that an audio application can achieve in shared mode. | 617 // that an audio application can achieve in shared mode. |
630 REFERENCE_TIME default_device_period = 0; | 618 REFERENCE_TIME default_device_period = 0; |
631 REFERENCE_TIME minimum_device_period = 0; | 619 REFERENCE_TIME minimum_device_period = 0; |
632 HRESULT hr_dbg = audio_client_->GetDevicePeriod(&default_device_period, | 620 HRESULT hr_dbg = audio_client_->GetDevicePeriod(&default_device_period, |
633 &minimum_device_period); | 621 &minimum_device_period); |
634 if (SUCCEEDED(hr_dbg)) { | 622 if (SUCCEEDED(hr_dbg)) { |
635 // Shared mode device period. | 623 // Shared mode device period. |
636 DVLOG(1) << "default device period: " | 624 DVLOG(1) << "shared mode (default) device period: " |
637 << static_cast<double>(default_device_period / 10000.0) | 625 << static_cast<double>(default_device_period / 10000.0) |
638 << " [ms]"; | 626 << " [ms]"; |
639 // Exclusive mode device period. | 627 // Exclusive mode device period. |
640 DVLOG(1) << "minimum device period: " | 628 DVLOG(1) << "exclusive mode (minimum) device period: " |
641 << static_cast<double>(minimum_device_period / 10000.0) | 629 << static_cast<double>(minimum_device_period / 10000.0) |
642 << " [ms]"; | 630 << " [ms]"; |
643 } | 631 } |
644 | 632 |
645 REFERENCE_TIME latency = 0; | 633 REFERENCE_TIME latency = 0; |
646 hr_dbg = audio_client_->GetStreamLatency(&latency); | 634 hr_dbg = audio_client_->GetStreamLatency(&latency); |
647 if (SUCCEEDED(hr_dbg)) { | 635 if (SUCCEEDED(hr_dbg)) { |
648 DVLOG(1) << "stream latency: " << static_cast<double>(latency / 10000.0) | 636 DVLOG(1) << "stream latency: " << static_cast<double>(latency / 10000.0) |
649 << " [ms]"; | 637 << " [ms]"; |
650 } | 638 } |
651 #endif | 639 #endif |
652 | 640 |
| 641 HRESULT hr = S_FALSE; |
| 642 REFERENCE_TIME requested_buffer_duration = 0; |
| 643 |
| 644 // Perform different initialization depending on if the device shall be |
| 645 // opened in shared mode or in exclusive mode. |
| 646 if (share_mode() == AUDCLNT_SHAREMODE_SHARED) { |
| 647 // The device will be opened in shared mode and use the WAS format. |
| 648 |
| 649 // TODO(henrika): this buffer scheme is still under development. |
| 650 // The exact details are yet to be determined based on tests with different |
| 651 // audio clients. |
| 652 int glitch_free_buffer_size_ms = static_cast<int>(packet_size_ms_ + 0.5); |
| 653 if (audio_engine_mix_format_->nSamplesPerSec == 48000) { |
| 654 // Initial tests have shown that we have to add 10 ms extra to |
| 655 // ensure that we don't run empty for any packet size. |
| 656 glitch_free_buffer_size_ms += 10; |
| 657 } else if (audio_engine_mix_format_->nSamplesPerSec == 44100) { |
| 658 // Initial tests have shown that we have to add 20 ms extra to |
| 659 // ensure that we don't run empty for any packet size. |
| 660 glitch_free_buffer_size_ms += 20; |
| 661 } else { |
| 662 glitch_free_buffer_size_ms += 20; |
| 663 } |
| 664 DVLOG(1) << "glitch_free_buffer_size_ms: " << glitch_free_buffer_size_ms; |
| 665 requested_buffer_duration = |
| 666 static_cast<REFERENCE_TIME>(glitch_free_buffer_size_ms * 10000); |
| 667 |
| 668 // Initialize the audio stream between the client and the device. |
| 669 // We connect indirectly through the audio engine by using shared mode |
| 670 // and WASAPI is initialized in an event driven mode. |
| 671 // Note that this API ensures that the buffer is never smaller than the |
| 672 // minimum buffer size needed to ensure glitch-free rendering. |
| 673 // If we requests a buffer size that is smaller than the audio engine's |
| 674 // minimum required buffer size, the method sets the buffer size to this |
| 675 // minimum buffer size rather than to the buffer size requested. |
| 676 hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, |
| 677 AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
| 678 AUDCLNT_STREAMFLAGS_NOPERSIST, |
| 679 requested_buffer_duration, |
| 680 0, |
| 681 &format_, |
| 682 NULL); |
| 683 } else { |
| 684 // The device will be opened in exclusive mode and use the application |
| 685 // specified format. |
| 686 |
| 687 float f = (1000.0 * packet_size_frames_) / format_.nSamplesPerSec; |
| 688 requested_buffer_duration = static_cast<REFERENCE_TIME>(f*10000.0 + 0.5); |
| 689 |
| 690 // Initialize the audio stream between the client and the device. |
| 691 // For an exclusive-mode stream that uses event-driven buffering, the |
| 692 // caller must specify nonzero values for hnsPeriodicity and |
| 693 // hnsBufferDuration, and the values of these two parameters must be equal. |
| 694 // The Initialize method allocates two buffers for the stream. Each buffer |
| 695 // is equal in duration to the value of the hnsBufferDuration parameter. |
| 696 // Following the Initialize call for a rendering stream, the caller should |
| 697 // fill the first of the two buffers before starting the stream. |
| 698 hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, |
| 699 AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
| 700 AUDCLNT_STREAMFLAGS_NOPERSIST, |
| 701 requested_buffer_duration, |
| 702 requested_buffer_duration, |
| 703 &format_, |
| 704 NULL); |
| 705 if (FAILED(hr)) { |
| 706 if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { |
| 707 DLOG(ERROR) << "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; |
| 708 |
| 709 UINT32 aligned_buffer_size = 0; |
| 710 audio_client_->GetBufferSize(&aligned_buffer_size); |
| 711 DVLOG(1) << "Use aligned buffer size instead: " << aligned_buffer_size; |
| 712 audio_client_.Release(); |
| 713 |
| 714 // Calculate new aligned periodicity. Each unit of reference time |
| 715 // is 100 nanoseconds. |
| 716 REFERENCE_TIME aligned_buffer_duration = static_cast<REFERENCE_TIME>( |
| 717 10000000.0 * aligned_buffer_size / format_.nSamplesPerSec + 0.5); |
| 718 |
| 719 // It is possible to re-activate and re-initialize the audio client |
| 720 // at this stage but we bail out with an error code instead and |
| 721 // combine it with a log message which informs about the suggested |
| 722 // aligned buffer size which should be used instead. |
| 723 DVLOG(1) << "aligned_buffer_duration: " |
| 724 << static_cast<double>(aligned_buffer_duration / 10000.0) |
| 725 << " [ms]"; |
| 726 } else if (hr == AUDCLNT_E_INVALID_DEVICE_PERIOD) { |
| 727 // We will get this error if we try to use a smaller buffer size than |
| 728 // the minimum supported size (usually ~3ms on Windows 7). |
| 729 DLOG(ERROR) << "AUDCLNT_E_INVALID_DEVICE_PERIOD"; |
| 730 } |
| 731 } |
| 732 } |
| 733 |
| 734 if (FAILED(hr)) { |
| 735 DVLOG(1) << "IAudioClient::Initialize() failed: " << std::hex << hr; |
| 736 return hr; |
| 737 } |
| 738 |
| 739 // Retrieve the length of the endpoint buffer. The buffer length represents |
| 740 // the maximum amount of rendering data that the client can write to |
| 741 // the endpoint buffer during a single processing pass. |
| 742 // A typical value is 960 audio frames <=> 20ms @ 48kHz sample rate. |
| 743 hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_); |
| 744 if (FAILED(hr)) |
| 745 return hr; |
| 746 DVLOG(1) << "endpoint buffer size: " << endpoint_buffer_size_frames_ |
| 747 << " [frames]"; |
| 748 |
| 749 // The buffer scheme for exclusive mode streams is not designed for max |
| 750 // flexibility. We only allow a "perfect match" between the packet size set |
| 751 // by the user and the actual endpoint buffer size. |
| 752 if (share_mode() == AUDCLNT_SHAREMODE_EXCLUSIVE) { |
| 753 if (endpoint_buffer_size_frames_ != packet_size_frames_) { |
| 754 hr = AUDCLNT_E_INVALID_SIZE; |
| 755 DLOG(ERROR) << "AUDCLNT_E_INVALID_SIZE"; |
| 756 return hr; |
| 757 } |
| 758 } |
| 759 |
653 // Set the event handle that the audio engine will signal each time | 760 // Set the event handle that the audio engine will signal each time |
654 // a buffer becomes ready to be processed by the client. | 761 // a buffer becomes ready to be processed by the client. |
655 hr = audio_client_->SetEventHandle(audio_samples_render_event_.Get()); | 762 hr = audio_client_->SetEventHandle(audio_samples_render_event_.Get()); |
656 if (FAILED(hr)) | 763 if (FAILED(hr)) |
657 return hr; | 764 return hr; |
658 | 765 |
659 // Get access to the IAudioRenderClient interface. This interface | 766 // Get access to the IAudioRenderClient interface. This interface |
660 // enables us to write output data to a rendering endpoint buffer. | 767 // enables us to write output data to a rendering endpoint buffer. |
661 // The methods in this interface manage the movement of data packets | 768 // The methods in this interface manage the movement of data packets |
662 // that contain audio-rendering data. | 769 // that contain audio-rendering data. |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
823 // are now re-initiated and it is now possible to re-start audio rendering. | 930 // are now re-initiated and it is now possible to re-start audio rendering. |
824 | 931 |
825 // Start rendering again using the new default audio endpoint. | 932 // Start rendering again using the new default audio endpoint. |
826 hr = audio_client_->Start(); | 933 hr = audio_client_->Start(); |
827 | 934 |
828 restart_rendering_mode_ = false; | 935 restart_rendering_mode_ = false; |
829 return SUCCEEDED(hr); | 936 return SUCCEEDED(hr); |
830 } | 937 } |
831 | 938 |
832 } // namespace media | 939 } // namespace media |
OLD | NEW |