[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 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 |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | 91d3a7d | 2012-08-22 00:56:55 | [diff] [blame] | 5 | #include "remoting/host/audio_capturer_win.h" |
| 6 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 7 | #include <avrt.h> |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 8 | #include <mmreg.h> |
| 9 | #include <mmsystem.h> |
robliao | 6fc41c6 | 2017-04-28 22:41:17 | [diff] [blame] | 10 | #include <objbase.h> |
avi | c5960f3 | 2015-12-22 22:49:48 | [diff] [blame] | 11 | #include <stdint.h> |
[email protected] | 07cb35f | 2012-07-31 23:10:10 | [diff] [blame] | 12 | #include <stdlib.h> |
sergeyu | 1417e013 | 2015-12-23 19:01:22 | [diff] [blame] | 13 | #include <windows.h> |
| 14 | |
avi | c5960f3 | 2015-12-22 22:49:48 | [diff] [blame] | 15 | #include <algorithm> |
sergeyu | 1417e013 | 2015-12-23 19:01:22 | [diff] [blame] | 16 | #include <utility> |
[email protected] | 07cb35f | 2012-07-31 23:10:10 | [diff] [blame] | 17 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 18 | #include "base/logging.h" |
dcheng | 0765c49 | 2016-04-06 22:41:53 | [diff] [blame] | 19 | #include "base/memory/ptr_util.h" |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 20 | #include "base/synchronization/lock.h" |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 21 | #include "remoting/host/win/default_audio_device_change_detector.h" |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 22 | |
| 23 | namespace { |
[email protected] | 881de5e | 2013-01-02 23:11:14 | [diff] [blame] | 24 | const int kBytesPerSample = 2; |
| 25 | const int kBitsPerSample = kBytesPerSample * 8; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 26 | // Conversion factor from 100ns to 1ms. |
[email protected] | 37a770b0 | 2012-08-17 00:13:17 | [diff] [blame] | 27 | const int k100nsPerMillisecond = 10000; |
[email protected] | 07cb35f | 2012-07-31 23:10:10 | [diff] [blame] | 28 | |
| 29 | // Tolerance for catching packets of silence. If all samples have absolute |
| 30 | // value less than this threshold, the packet will be counted as a packet of |
| 31 | // silence. A value of 2 was chosen, because Windows can give samples of 1 and |
| 32 | // -1, even when no audio is playing. |
| 33 | const int kSilenceThreshold = 2; |
[email protected] | 37a770b0 | 2012-08-17 00:13:17 | [diff] [blame] | 34 | |
| 35 | // Lower bound for timer intervals, in milliseconds. |
| 36 | const int kMinTimerInterval = 30; |
| 37 | |
| 38 | // Upper bound for the timer precision error, in milliseconds. |
| 39 | // Timers are supposed to be accurate to 20ms, so we use 30ms to be safe. |
| 40 | const int kMaxExpectedTimerLag = 30; |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 41 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 42 | } // namespace |
| 43 | |
| 44 | namespace remoting { |
| 45 | |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame] | 46 | AudioCapturerWin::AudioCapturerWin() |
[email protected] | 881de5e | 2013-01-02 23:11:14 | [diff] [blame] | 47 | : sampling_rate_(AudioPacket::SAMPLING_RATE_INVALID), |
zijiehe | 26fe0da | 2017-05-23 04:34:11 | [diff] [blame] | 48 | volume_filter_(kSilenceThreshold), |
[email protected] | a21883d | 2013-03-16 09:14:51 | [diff] [blame] | 49 | last_capture_error_(S_OK) { |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 50 | thread_checker_.DetachFromThread(); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | AudioCapturerWin::~AudioCapturerWin() { |
sergeyu | 7bc8760 | 2015-02-13 20:33:28 | [diff] [blame] | 54 | DCHECK(thread_checker_.CalledOnValidThread()); |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 55 | Deinitialize(); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 56 | } |
| 57 | |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 58 | bool AudioCapturerWin::Start(const PacketCapturedCallback& callback) { |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 59 | callback_ = callback; |
| 60 | |
| 61 | if (!Initialize()) { |
| 62 | return false; |
| 63 | } |
| 64 | |
| 65 | // Initialize the capture timer and start capturing. Note, this timer won't |
| 66 | // be reset or restarted in ResetAndInitialize() function. Which means we |
| 67 | // expect the audio_device_period_ is a system wide configuration, it would |
| 68 | // not be changed with the default audio device. |
| 69 | capture_timer_.reset(new base::RepeatingTimer()); |
| 70 | capture_timer_->Start(FROM_HERE, audio_device_period_, this, |
| 71 | &AudioCapturerWin::DoCapture); |
| 72 | return true; |
| 73 | } |
| 74 | |
| 75 | bool AudioCapturerWin::ResetAndInitialize() { |
| 76 | Deinitialize(); |
| 77 | if (!Initialize()) { |
| 78 | Deinitialize(); |
| 79 | return false; |
| 80 | } |
| 81 | return true; |
| 82 | } |
| 83 | |
| 84 | void AudioCapturerWin::Deinitialize() { |
| 85 | DCHECK(thread_checker_.CalledOnValidThread()); |
| 86 | wave_format_ex_.Reset(nullptr); |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 87 | default_device_detector_.reset(); |
robliao | 9212cb4 | 2017-04-06 17:43:39 | [diff] [blame] | 88 | audio_capture_client_.Reset(); |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 89 | if (audio_client_) { |
| 90 | audio_client_->Stop(); |
| 91 | } |
robliao | 9212cb4 | 2017-04-06 17:43:39 | [diff] [blame] | 92 | audio_client_.Reset(); |
| 93 | mm_device_.Reset(); |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | bool AudioCapturerWin::Initialize() { |
robliao | b44b907 | 2017-04-19 00:28:09 | [diff] [blame] | 97 | DCHECK(!audio_capture_client_.Get()); |
| 98 | DCHECK(!audio_client_.Get()); |
| 99 | DCHECK(!mm_device_.Get()); |
sergeyu | c5f104b | 2015-01-09 19:33:24 | [diff] [blame] | 100 | DCHECK(static_cast<PWAVEFORMATEX>(wave_format_ex_) == nullptr); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 101 | DCHECK(thread_checker_.CalledOnValidThread()); |
| 102 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 103 | HRESULT hr = S_OK; |
Robert Liao | 1a37835 | 2017-10-18 01:31:17 | [diff] [blame] | 104 | Microsoft::WRL::ComPtr<IMMDeviceEnumerator> mm_device_enumerator; |
robliao | eb9bfd64 | 2017-05-18 17:35:16 | [diff] [blame] | 105 | hr = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, |
| 106 | IID_PPV_ARGS(&mm_device_enumerator)); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 107 | if (FAILED(hr)) { |
| 108 | LOG(ERROR) << "Failed to create IMMDeviceEnumerator. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 109 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 110 | } |
| 111 | |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 112 | default_device_detector_.reset( |
| 113 | new DefaultAudioDeviceChangeDetector(mm_device_enumerator)); |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 114 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 115 | // Get the audio endpoint. |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 116 | hr = mm_device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, |
robliao | 310fa98b | 2017-05-11 17:14:00 | [diff] [blame] | 117 | mm_device_.GetAddressOf()); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 118 | if (FAILED(hr)) { |
| 119 | LOG(ERROR) << "Failed to get IMMDevice. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 120 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 121 | } |
| 122 | |
| 123 | // Get an audio client. |
| 124 | hr = mm_device_->Activate(__uuidof(IAudioClient), |
| 125 | CLSCTX_ALL, |
sergeyu | c5f104b | 2015-01-09 19:33:24 | [diff] [blame] | 126 | nullptr, |
robliao | 6fc41c6 | 2017-04-28 22:41:17 | [diff] [blame] | 127 | &audio_client_); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 128 | if (FAILED(hr)) { |
| 129 | LOG(ERROR) << "Failed to get an IAudioClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 130 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 131 | } |
| 132 | |
| 133 | REFERENCE_TIME device_period; |
sergeyu | c5f104b | 2015-01-09 19:33:24 | [diff] [blame] | 134 | hr = audio_client_->GetDevicePeriod(&device_period, nullptr); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 135 | if (FAILED(hr)) { |
| 136 | LOG(ERROR) << "IAudioClient::GetDevicePeriod failed. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 137 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 138 | } |
[email protected] | 37a770b0 | 2012-08-17 00:13:17 | [diff] [blame] | 139 | // We round up, if |device_period| / |k100nsPerMillisecond| |
| 140 | // is not a whole number. |
| 141 | int device_period_in_milliseconds = |
| 142 | 1 + ((device_period - 1) / k100nsPerMillisecond); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 143 | audio_device_period_ = base::TimeDelta::FromMilliseconds( |
[email protected] | 37a770b0 | 2012-08-17 00:13:17 | [diff] [blame] | 144 | std::max(device_period_in_milliseconds, kMinTimerInterval)); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 145 | |
| 146 | // Get the wave format. |
| 147 | hr = audio_client_->GetMixFormat(&wave_format_ex_); |
| 148 | if (FAILED(hr)) { |
| 149 | LOG(ERROR) << "Failed to get WAVEFORMATEX. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 150 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 151 | } |
| 152 | |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 153 | if (wave_format_ex_->wFormatTag != WAVE_FORMAT_IEEE_FLOAT && |
| 154 | wave_format_ex_->wFormatTag != WAVE_FORMAT_PCM && |
| 155 | wave_format_ex_->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { |
| 156 | LOG(ERROR) << "Failed to force 16-bit PCM"; |
| 157 | return false; |
| 158 | } |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame] | 159 | |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 160 | if (!AudioCapturer::IsValidSampleRate(wave_format_ex_->nSamplesPerSec)) { |
| 161 | LOG(ERROR) << "Host sampling rate is neither 44.1 kHz nor 48 kHz. " |
| 162 | << wave_format_ex_->nSamplesPerSec; |
| 163 | return false; |
| 164 | } |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame] | 165 | |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 166 | // We support from mono to 7.1. This check should be consistent with |
| 167 | // AudioPacket::Channels. |
| 168 | if (wave_format_ex_->nChannels > 8 || wave_format_ex_->nChannels <= 0) { |
| 169 | LOG(ERROR) << "Unsupported channels " << wave_format_ex_->nChannels; |
| 170 | return false; |
| 171 | } |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 172 | |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 173 | sampling_rate_ = static_cast<AudioPacket::SamplingRate>( |
| 174 | wave_format_ex_->nSamplesPerSec); |
| 175 | |
| 176 | wave_format_ex_->wBitsPerSample = kBitsPerSample; |
| 177 | wave_format_ex_->nBlockAlign = wave_format_ex_->nChannels * kBytesPerSample; |
| 178 | wave_format_ex_->nAvgBytesPerSec = |
| 179 | sampling_rate_ * wave_format_ex_->nBlockAlign; |
| 180 | |
| 181 | if (wave_format_ex_->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { |
| 182 | PWAVEFORMATEXTENSIBLE wave_format_extensible = |
| 183 | reinterpret_cast<WAVEFORMATEXTENSIBLE*>( |
| 184 | static_cast<WAVEFORMATEX*>(wave_format_ex_)); |
| 185 | if (!IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, |
| 186 | wave_format_extensible->SubFormat) && |
| 187 | !IsEqualGUID(KSDATAFORMAT_SUBTYPE_PCM, |
| 188 | wave_format_extensible->SubFormat)) { |
| 189 | LOG(ERROR) << "Failed to force 16-bit samples"; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 190 | return false; |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | wave_format_extensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; |
| 194 | wave_format_extensible->Samples.wValidBitsPerSample = kBitsPerSample; |
| 195 | } else { |
| 196 | wave_format_ex_->wFormatTag = WAVE_FORMAT_PCM; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 197 | } |
| 198 | |
| 199 | // Initialize the IAudioClient. |
[email protected] | 37a770b0 | 2012-08-17 00:13:17 | [diff] [blame] | 200 | hr = audio_client_->Initialize( |
| 201 | AUDCLNT_SHAREMODE_SHARED, |
| 202 | AUDCLNT_STREAMFLAGS_LOOPBACK, |
| 203 | (kMaxExpectedTimerLag + audio_device_period_.InMilliseconds()) * |
| 204 | k100nsPerMillisecond, |
| 205 | 0, |
| 206 | wave_format_ex_, |
sergeyu | c5f104b | 2015-01-09 19:33:24 | [diff] [blame] | 207 | nullptr); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 208 | if (FAILED(hr)) { |
| 209 | LOG(ERROR) << "Failed to initialize IAudioClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 210 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | // Get an IAudioCaptureClient. |
robliao | 6fc41c6 | 2017-04-28 22:41:17 | [diff] [blame] | 214 | hr = audio_client_->GetService(IID_PPV_ARGS(&audio_capture_client_)); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 215 | if (FAILED(hr)) { |
| 216 | LOG(ERROR) << "Failed to get an IAudioCaptureClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 217 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | // Start the IAudioClient. |
| 221 | hr = audio_client_->Start(); |
| 222 | if (FAILED(hr)) { |
| 223 | LOG(ERROR) << "Failed to start IAudioClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 224 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 225 | } |
| 226 | |
zijiehe | 26fe0da | 2017-05-23 04:34:11 | [diff] [blame] | 227 | volume_filter_.ActivateBy(mm_device_.Get()); |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 228 | volume_filter_.Initialize(sampling_rate_, wave_format_ex_->nChannels); |
[email protected] | 881de5e | 2013-01-02 23:11:14 | [diff] [blame] | 229 | |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 230 | return true; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 231 | } |
| 232 | |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 233 | bool AudioCapturerWin::is_initialized() const { |
| 234 | // All Com components should be initialized / deinitialized together. |
zijiehe | 26fe0da | 2017-05-23 04:34:11 | [diff] [blame] | 235 | return !!audio_client_; |
zijiehe | 3ba26c00 | 2016-03-10 20:49:24 | [diff] [blame] | 236 | } |
| 237 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 238 | void AudioCapturerWin::DoCapture() { |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame] | 239 | DCHECK(AudioCapturer::IsValidSampleRate(sampling_rate_)); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 240 | DCHECK(thread_checker_.CalledOnValidThread()); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 241 | |
zijiehe | 00c53fe | 2017-04-24 23:33:05 | [diff] [blame] | 242 | if (!is_initialized() || default_device_detector_->GetAndReset()) { |
zijiehe | b40182a | 2016-07-24 19:28:15 | [diff] [blame] | 243 | if (!ResetAndInitialize()) { |
| 244 | // Initialization failed, we should wait for next DoCapture call. |
| 245 | return; |
| 246 | } |
| 247 | } |
| 248 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 249 | // Fetch all packets from the audio capture endpoint buffer. |
[email protected] | a21883d | 2013-03-16 09:14:51 | [diff] [blame] | 250 | HRESULT hr = S_OK; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 251 | while (true) { |
| 252 | UINT32 next_packet_size; |
| 253 | HRESULT hr = audio_capture_client_->GetNextPacketSize(&next_packet_size); |
[email protected] | a21883d | 2013-03-16 09:14:51 | [diff] [blame] | 254 | if (FAILED(hr)) |
| 255 | break; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 256 | |
| 257 | if (next_packet_size <= 0) { |
| 258 | return; |
| 259 | } |
| 260 | |
| 261 | BYTE* data; |
| 262 | UINT32 frames; |
| 263 | DWORD flags; |
sergeyu | c5f104b | 2015-01-09 19:33:24 | [diff] [blame] | 264 | hr = audio_capture_client_->GetBuffer(&data, &frames, &flags, nullptr, |
| 265 | nullptr); |
[email protected] | a21883d | 2013-03-16 09:14:51 | [diff] [blame] | 266 | if (FAILED(hr)) |
| 267 | break; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 268 | |
zijiehe | 26fe0da | 2017-05-23 04:34:11 | [diff] [blame] | 269 | if (volume_filter_.Apply(reinterpret_cast<int16_t*>(data), frames)) { |
| 270 | std::unique_ptr<AudioPacket> packet(new AudioPacket()); |
| 271 | packet->add_data(data, frames * wave_format_ex_->nBlockAlign); |
| 272 | packet->set_encoding(AudioPacket::ENCODING_RAW); |
| 273 | packet->set_sampling_rate(sampling_rate_); |
| 274 | packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2); |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 275 | // Only the count of channels is taken into account now, we should also |
| 276 | // consider dwChannelMask. |
zijiehe | 2fc3d05 | 2017-06-13 03:27:42 | [diff] [blame] | 277 | // TODO(zijiehe): Convert dwChannelMask to layout and pass it to |
| 278 | // AudioPump. So the stream can be downmixed properly with both number and |
| 279 | // layouts of speakers. |
zijiehe | c331598 | 2017-06-10 01:22:09 | [diff] [blame] | 280 | packet->set_channels(static_cast<AudioPacket::Channels>( |
| 281 | wave_format_ex_->nChannels)); |
zijiehe | 26fe0da | 2017-05-23 04:34:11 | [diff] [blame] | 282 | |
| 283 | callback_.Run(std::move(packet)); |
| 284 | } |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 285 | |
| 286 | hr = audio_capture_client_->ReleaseBuffer(frames); |
[email protected] | a21883d | 2013-03-16 09:14:51 | [diff] [blame] | 287 | if (FAILED(hr)) |
| 288 | break; |
| 289 | } |
| 290 | |
| 291 | // There is nothing to capture if the audio endpoint device has been unplugged |
| 292 | // or disabled. |
| 293 | if (hr == AUDCLNT_E_DEVICE_INVALIDATED) |
| 294 | return; |
| 295 | |
| 296 | // Avoid reporting the same error multiple times. |
| 297 | if (FAILED(hr) && hr != last_capture_error_) { |
| 298 | last_capture_error_ = hr; |
| 299 | LOG(ERROR) << "Failed to capture an audio packet: 0x" |
| 300 | << std::hex << hr << std::dec << "."; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 301 | } |
| 302 | } |
| 303 | |
[email protected] | e8aaf2ca | 2012-09-11 18:07:24 | [diff] [blame] | 304 | bool AudioCapturer::IsSupported() { |
| 305 | return true; |
| 306 | } |
| 307 | |
dcheng | 0765c49 | 2016-04-06 22:41:53 | [diff] [blame] | 308 | std::unique_ptr<AudioCapturer> AudioCapturer::Create() { |
| 309 | return base::WrapUnique(new AudioCapturerWin()); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 310 | } |
| 311 | |
| 312 | } // namespace remoting |