| 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/command_line.h" | 9 #include "base/command_line.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| (...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 // All events are auto-reset events and non-signaled initially. | 232 // All events are auto-reset events and non-signaled initially. |
| 233 | 233 |
| 234 // Create the event which the audio engine will signal each time | 234 // Create the event which the audio engine will signal each time |
| 235 // a buffer becomes ready to be processed by the client. | 235 // a buffer becomes ready to be processed by the client. |
| 236 audio_samples_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); | 236 audio_samples_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); |
| 237 DCHECK(audio_samples_render_event_.IsValid()); | 237 DCHECK(audio_samples_render_event_.IsValid()); |
| 238 | 238 |
| 239 // Create the event which will be set in Stop() when capturing shall stop. | 239 // Create the event which will be set in Stop() when capturing shall stop. |
| 240 stop_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); | 240 stop_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); |
| 241 DCHECK(stop_render_event_.IsValid()); | 241 DCHECK(stop_render_event_.IsValid()); |
| 242 | |
| 243 // Create the event which will be set when a stream switch shall take place. | |
| 244 stream_switch_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); | |
| 245 DCHECK(stream_switch_event_.IsValid()); | |
| 246 } | 242 } |
| 247 | 243 |
| 248 WASAPIAudioOutputStream::~WASAPIAudioOutputStream() {} | 244 WASAPIAudioOutputStream::~WASAPIAudioOutputStream() {} |
| 249 | 245 |
| 250 bool WASAPIAudioOutputStream::Open() { | 246 bool WASAPIAudioOutputStream::Open() { |
| 251 DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); | 247 DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); |
| 252 if (opened_) | 248 if (opened_) |
| 253 return true; | 249 return true; |
| 254 | 250 |
| 255 // Channel mixing is not supported, it must be handled by ChannelMixer. | 251 // Channel mixing is not supported, it must be handled by ChannelMixer. |
| (...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 477 // to reduced QoS at high load. | 473 // to reduced QoS at high load. |
| 478 DWORD err = GetLastError(); | 474 DWORD err = GetLastError(); |
| 479 LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ")."; | 475 LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ")."; |
| 480 } | 476 } |
| 481 | 477 |
| 482 HRESULT hr = S_FALSE; | 478 HRESULT hr = S_FALSE; |
| 483 | 479 |
| 484 bool playing = true; | 480 bool playing = true; |
| 485 bool error = false; | 481 bool error = false; |
| 486 HANDLE wait_array[] = { stop_render_event_, | 482 HANDLE wait_array[] = { stop_render_event_, |
| 487 stream_switch_event_, | |
| 488 audio_samples_render_event_ }; | 483 audio_samples_render_event_ }; |
| 489 UINT64 device_frequency = 0; | 484 UINT64 device_frequency = 0; |
| 490 | 485 |
| 491 // The IAudioClock interface enables us to monitor a stream's data | 486 // The IAudioClock interface enables us to monitor a stream's data |
| 492 // rate and the current position in the stream. Allocate it before we | 487 // rate and the current position in the stream. Allocate it before we |
| 493 // start spinning. | 488 // start spinning. |
| 494 ScopedComPtr<IAudioClock> audio_clock; | 489 ScopedComPtr<IAudioClock> audio_clock; |
| 495 hr = audio_client_->GetService(__uuidof(IAudioClock), | 490 hr = audio_client_->GetService(__uuidof(IAudioClock), |
| 496 audio_clock.ReceiveVoid()); | 491 audio_clock.ReceiveVoid()); |
| 497 if (SUCCEEDED(hr)) { | 492 if (SUCCEEDED(hr)) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 511 wait_array, | 506 wait_array, |
| 512 FALSE, | 507 FALSE, |
| 513 INFINITE); | 508 INFINITE); |
| 514 | 509 |
| 515 switch (wait_result) { | 510 switch (wait_result) { |
| 516 case WAIT_OBJECT_0 + 0: | 511 case WAIT_OBJECT_0 + 0: |
| 517 // |stop_render_event_| has been set. | 512 // |stop_render_event_| has been set. |
| 518 playing = false; | 513 playing = false; |
| 519 break; | 514 break; |
| 520 case WAIT_OBJECT_0 + 1: | 515 case WAIT_OBJECT_0 + 1: |
| 521 // |stream_switch_event_| has been set. Stop rendering and try to | |
| 522 // re-start the session using a new endpoint device. | |
| 523 if (!RestartRenderingUsingNewDefaultDevice()) { | |
| 524 // Abort the thread since stream switching failed. | |
| 525 playing = false; | |
| 526 error = true; | |
| 527 } | |
| 528 break; | |
| 529 case WAIT_OBJECT_0 + 2: | |
| 530 { | 516 { |
| 531 // |audio_samples_render_event_| has been set. | 517 // |audio_samples_render_event_| has been set. |
| 532 UINT32 num_queued_frames = 0; | 518 UINT32 num_queued_frames = 0; |
| 533 uint8* audio_data = NULL; | 519 uint8* audio_data = NULL; |
| 534 | 520 |
| 535 // Contains how much new data we can write to the buffer without | 521 // Contains how much new data we can write to the buffer without |
| 536 // the risk of overwriting previously written data that the audio | 522 // the risk of overwriting previously written data that the audio |
| 537 // engine has not yet read from the buffer. | 523 // engine has not yet read from the buffer. |
| 538 size_t num_available_frames = 0; | 524 size_t num_available_frames = 0; |
| 539 | 525 |
| (...skipping 395 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 935 } else if (hr == AUDCLNT_E_INVALID_DEVICE_PERIOD) { | 921 } else if (hr == AUDCLNT_E_INVALID_DEVICE_PERIOD) { |
| 936 // We will get this error if we try to use a smaller buffer size than | 922 // We will get this error if we try to use a smaller buffer size than |
| 937 // the minimum supported size (usually ~3ms on Windows 7). | 923 // the minimum supported size (usually ~3ms on Windows 7). |
| 938 LOG(ERROR) << "AUDCLNT_E_INVALID_DEVICE_PERIOD"; | 924 LOG(ERROR) << "AUDCLNT_E_INVALID_DEVICE_PERIOD"; |
| 939 } | 925 } |
| 940 } | 926 } |
| 941 | 927 |
| 942 return hr; | 928 return hr; |
| 943 } | 929 } |
| 944 | 930 |
| 945 ULONG WASAPIAudioOutputStream::AddRef() { | |
| 946 NOTREACHED() << "IMMNotificationClient should not use this method."; | |
| 947 return 1; | |
| 948 } | |
| 949 | |
| 950 ULONG WASAPIAudioOutputStream::Release() { | |
| 951 NOTREACHED() << "IMMNotificationClient should not use this method."; | |
| 952 return 1; | |
| 953 } | |
| 954 | |
| 955 HRESULT WASAPIAudioOutputStream::QueryInterface(REFIID iid, void** object) { | |
| 956 NOTREACHED() << "IMMNotificationClient should not use this method."; | |
| 957 if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) { | |
| 958 *object = static_cast < IMMNotificationClient*>(this); | |
| 959 } else { | |
| 960 return E_NOINTERFACE; | |
| 961 } | |
| 962 return S_OK; | |
| 963 } | |
| 964 | |
| 965 STDMETHODIMP WASAPIAudioOutputStream::OnDeviceStateChanged(LPCWSTR device_id, | |
| 966 DWORD new_state) { | |
| 967 #ifndef NDEBUG | |
| 968 std::string device_name = GetDeviceName(device_id); | |
| 969 std::string device_state; | |
| 970 | |
| 971 switch (new_state) { | |
| 972 case DEVICE_STATE_ACTIVE: | |
| 973 device_state = "ACTIVE"; | |
| 974 break; | |
| 975 case DEVICE_STATE_DISABLED: | |
| 976 device_state = "DISABLED"; | |
| 977 break; | |
| 978 case DEVICE_STATE_NOTPRESENT: | |
| 979 device_state = "NOTPRESENT"; | |
| 980 break; | |
| 981 case DEVICE_STATE_UNPLUGGED: | |
| 982 device_state = "UNPLUGGED"; | |
| 983 break; | |
| 984 default: | |
| 985 break; | |
| 986 } | |
| 987 | |
| 988 DVLOG(1) << "-> State changed to " << device_state | |
| 989 << " for device: " << device_name; | |
| 990 #endif | |
| 991 return S_OK; | |
| 992 } | |
| 993 | |
| 994 HRESULT WASAPIAudioOutputStream::OnDefaultDeviceChanged( | |
| 995 EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { | |
| 996 if (new_default_device_id == NULL) { | |
| 997 // The user has removed or disabled the default device for our | |
| 998 // particular role, and no other device is available to take that role. | |
| 999 DLOG(ERROR) << "All devices are disabled."; | |
| 1000 return E_FAIL; | |
| 1001 } | |
| 1002 | |
| 1003 if (flow == eRender && role == device_role_) { | |
| 1004 // Log the name of the new default device for our configured role. | |
| 1005 std::string new_default_device = GetDeviceName(new_default_device_id); | |
| 1006 DVLOG(1) << "-> New default device: " << new_default_device; | |
| 1007 | |
| 1008 // Initiate a stream switch if not already initiated by signaling the | |
| 1009 // stream-switch event to inform the render thread that it is OK to | |
| 1010 // re-initialize the active audio renderer. All the action takes place | |
| 1011 // on the WASAPI render thread. | |
| 1012 if (!restart_rendering_mode_) { | |
| 1013 restart_rendering_mode_ = true; | |
| 1014 SetEvent(stream_switch_event_.Get()); | |
| 1015 } | |
| 1016 } | |
| 1017 | |
| 1018 return S_OK; | |
| 1019 } | |
| 1020 | |
| 1021 std::string WASAPIAudioOutputStream::GetDeviceName(LPCWSTR device_id) const { | 931 std::string WASAPIAudioOutputStream::GetDeviceName(LPCWSTR device_id) const { |
| 1022 std::string name; | 932 std::string name; |
| 1023 ScopedComPtr<IMMDevice> audio_device; | 933 ScopedComPtr<IMMDevice> audio_device; |
| 1024 | 934 |
| 1025 // Get the IMMDevice interface corresponding to the given endpoint ID string. | 935 // Get the IMMDevice interface corresponding to the given endpoint ID string. |
| 1026 HRESULT hr = device_enumerator_->GetDevice(device_id, audio_device.Receive()); | 936 HRESULT hr = device_enumerator_->GetDevice(device_id, audio_device.Receive()); |
| 1027 if (SUCCEEDED(hr)) { | 937 if (SUCCEEDED(hr)) { |
| 1028 // Retrieve user-friendly name of endpoint device. | 938 // Retrieve user-friendly name of endpoint device. |
| 1029 // Example: "Speakers (Realtek High Definition Audio)". | 939 // Example: "Speakers (Realtek High Definition Audio)". |
| 1030 ScopedComPtr<IPropertyStore> properties; | 940 ScopedComPtr<IPropertyStore> properties; |
| 1031 hr = audio_device->OpenPropertyStore(STGM_READ, properties.Receive()); | 941 hr = audio_device->OpenPropertyStore(STGM_READ, properties.Receive()); |
| 1032 if (SUCCEEDED(hr)) { | 942 if (SUCCEEDED(hr)) { |
| 1033 PROPVARIANT friendly_name; | 943 PROPVARIANT friendly_name; |
| 1034 PropVariantInit(&friendly_name); | 944 PropVariantInit(&friendly_name); |
| 1035 hr = properties->GetValue(PKEY_Device_FriendlyName, &friendly_name); | 945 hr = properties->GetValue(PKEY_Device_FriendlyName, &friendly_name); |
| 1036 if (SUCCEEDED(hr) && friendly_name.vt == VT_LPWSTR) { | 946 if (SUCCEEDED(hr) && friendly_name.vt == VT_LPWSTR) { |
| 1037 if (friendly_name.pwszVal) | 947 if (friendly_name.pwszVal) |
| 1038 name = WideToUTF8(friendly_name.pwszVal); | 948 name = WideToUTF8(friendly_name.pwszVal); |
| 1039 } | 949 } |
| 1040 PropVariantClear(&friendly_name); | 950 PropVariantClear(&friendly_name); |
| 1041 } | 951 } |
| 1042 } | 952 } |
| 1043 return name; | 953 return name; |
| 1044 } | 954 } |
| 1045 | 955 |
| 1046 bool WASAPIAudioOutputStream::RestartRenderingUsingNewDefaultDevice() { | |
| 1047 DCHECK(base::PlatformThread::CurrentId() == render_thread_->tid()); | |
| 1048 DCHECK(restart_rendering_mode_); | |
| 1049 | |
| 1050 // The |restart_rendering_mode_| event has been signaled which means that | |
| 1051 // we must stop the current renderer and start a new render session using | |
| 1052 // the new default device with the configured role. | |
| 1053 | |
| 1054 // Stop the current rendering. | |
| 1055 HRESULT hr = audio_client_->Stop(); | |
| 1056 if (FAILED(hr)) { | |
| 1057 restart_rendering_mode_ = false; | |
| 1058 return false; | |
| 1059 } | |
| 1060 | |
| 1061 // Release acquired interfaces (IAudioRenderClient, IAudioClient, IMMDevice). | |
| 1062 audio_render_client_.Release(); | |
| 1063 audio_client_.Release(); | |
| 1064 endpoint_device_.Release(); | |
| 1065 | |
| 1066 // Retrieve the new default render audio endpoint (IMMDevice) for the | |
| 1067 // specified role. | |
| 1068 hr = device_enumerator_->GetDefaultAudioEndpoint( | |
| 1069 eRender, device_role_, endpoint_device_.Receive()); | |
| 1070 if (FAILED(hr)) { | |
| 1071 restart_rendering_mode_ = false; | |
| 1072 return false; | |
| 1073 } | |
| 1074 | |
| 1075 // Re-create an IAudioClient interface. | |
| 1076 hr = ActivateRenderDevice(); | |
| 1077 if (FAILED(hr)) { | |
| 1078 restart_rendering_mode_ = false; | |
| 1079 return false; | |
| 1080 } | |
| 1081 | |
| 1082 // Retrieve the new mix format and ensure that it is supported given | |
| 1083 // the predefined format set at construction. | |
| 1084 base::win::ScopedCoMem<WAVEFORMATEX> new_audio_engine_mix_format; | |
| 1085 hr = audio_client_->GetMixFormat(&new_audio_engine_mix_format); | |
| 1086 if (FAILED(hr) || !DesiredFormatIsSupported()) { | |
| 1087 restart_rendering_mode_ = false; | |
| 1088 return false; | |
| 1089 } | |
| 1090 | |
| 1091 // Re-initialize the audio engine using the new audio endpoint. | |
| 1092 // This method will create a new IAudioRenderClient interface. | |
| 1093 hr = InitializeAudioEngine(); | |
| 1094 if (FAILED(hr)) { | |
| 1095 restart_rendering_mode_ = false; | |
| 1096 return false; | |
| 1097 } | |
| 1098 | |
| 1099 // All released interfaces (IAudioRenderClient, IAudioClient, IMMDevice) | |
| 1100 // are now re-initiated and it is now possible to re-start audio rendering. | |
| 1101 | |
| 1102 // Start rendering again using the new default audio endpoint. | |
| 1103 hr = audio_client_->Start(); | |
| 1104 | |
| 1105 restart_rendering_mode_ = false; | |
| 1106 return SUCCEEDED(hr); | |
| 1107 } | |
| 1108 | |
| 1109 } // namespace media | 956 } // namespace media |
| OLD | NEW |