[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 | |
| 5 | #include <windows.h> |
| 6 | #include <audioclient.h> |
| 7 | #include <avrt.h> |
| 8 | #include <mmdeviceapi.h> |
| 9 | #include <mmreg.h> |
| 10 | #include <mmsystem.h> |
| 11 | |
| 12 | #include "base/basictypes.h" |
| 13 | #include "base/logging.h" |
| 14 | #include "base/memory/scoped_ptr.h" |
| 15 | #include "base/message_loop.h" |
| 16 | #include "base/timer.h" |
| 17 | #include "base/win/scoped_co_mem.h" |
| 18 | #include "base/win/scoped_com_initializer.h" |
| 19 | #include "base/win/scoped_comptr.h" |
| 20 | #include "remoting/host/audio_capturer.h" |
| 21 | #include "remoting/proto/audio.pb.h" |
| 22 | |
| 23 | namespace { |
| 24 | const int kChannels = 2; |
| 25 | const int kBitsPerSample = 16; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 26 | const int kBitsPerByte = 8; |
| 27 | // Conversion factor from 100ns to 1ms. |
| 28 | const int kHnsToMs = 10000; |
| 29 | } // namespace |
| 30 | |
| 31 | namespace remoting { |
| 32 | |
| 33 | class AudioCapturerWin : public AudioCapturer { |
| 34 | public: |
| 35 | AudioCapturerWin(); |
| 36 | virtual ~AudioCapturerWin(); |
| 37 | |
| 38 | // AudioCapturer interface. |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 39 | virtual bool Start(const PacketCapturedCallback& callback) OVERRIDE; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 40 | virtual void Stop() OVERRIDE; |
| 41 | virtual bool IsRunning() OVERRIDE; |
| 42 | |
| 43 | private: |
| 44 | // Receives all packets from the audio capture endpoint buffer and pushes them |
| 45 | // to the network. |
| 46 | void DoCapture(); |
| 47 | |
| 48 | PacketCapturedCallback callback_; |
| 49 | |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 50 | AudioPacket::SamplingRate sampling_rate_; |
| 51 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 52 | scoped_ptr<base::RepeatingTimer<AudioCapturerWin> > capture_timer_; |
| 53 | base::TimeDelta audio_device_period_; |
| 54 | |
| 55 | base::win::ScopedCoMem<WAVEFORMATEX> wave_format_ex_; |
| 56 | base::win::ScopedComPtr<IAudioCaptureClient> audio_capture_client_; |
| 57 | base::win::ScopedComPtr<IAudioClient> audio_client_; |
| 58 | base::win::ScopedComPtr<IMMDevice> mm_device_; |
| 59 | scoped_ptr<base::win::ScopedCOMInitializer> com_initializer_; |
| 60 | |
| 61 | base::ThreadChecker thread_checker_; |
| 62 | |
| 63 | DISALLOW_COPY_AND_ASSIGN(AudioCapturerWin); |
| 64 | }; |
| 65 | |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 66 | AudioCapturerWin::AudioCapturerWin() |
| 67 | : sampling_rate_(AudioPacket::SAMPLING_RATE_INVALID) { |
| 68 | thread_checker_.DetachFromThread(); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 69 | } |
| 70 | |
| 71 | AudioCapturerWin::~AudioCapturerWin() { |
| 72 | } |
| 73 | |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 74 | bool AudioCapturerWin::Start(const PacketCapturedCallback& callback) { |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 75 | DCHECK(!audio_capture_client_.get()); |
| 76 | DCHECK(!audio_client_.get()); |
| 77 | DCHECK(!mm_device_.get()); |
| 78 | DCHECK(static_cast<PWAVEFORMATEX>(wave_format_ex_) == NULL); |
| 79 | DCHECK(thread_checker_.CalledOnValidThread()); |
| 80 | |
| 81 | callback_ = callback; |
| 82 | |
| 83 | // Initialize the capture timer. |
| 84 | capture_timer_.reset(new base::RepeatingTimer<AudioCapturerWin>()); |
| 85 | |
| 86 | HRESULT hr = S_OK; |
| 87 | com_initializer_.reset(new base::win::ScopedCOMInitializer()); |
| 88 | |
| 89 | base::win::ScopedComPtr<IMMDeviceEnumerator> mm_device_enumerator; |
| 90 | hr = mm_device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator)); |
| 91 | if (FAILED(hr)) { |
| 92 | LOG(ERROR) << "Failed to create IMMDeviceEnumerator. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 93 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | // Get the audio endpoint. |
| 97 | hr = mm_device_enumerator->GetDefaultAudioEndpoint(eRender, |
| 98 | eConsole, |
| 99 | mm_device_.Receive()); |
| 100 | if (FAILED(hr)) { |
| 101 | LOG(ERROR) << "Failed to get IMMDevice. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 102 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | // Get an audio client. |
| 106 | hr = mm_device_->Activate(__uuidof(IAudioClient), |
| 107 | CLSCTX_ALL, |
| 108 | NULL, |
| 109 | audio_client_.ReceiveVoid()); |
| 110 | if (FAILED(hr)) { |
| 111 | LOG(ERROR) << "Failed to get an IAudioClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 112 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 113 | } |
| 114 | |
| 115 | REFERENCE_TIME device_period; |
| 116 | hr = audio_client_->GetDevicePeriod(&device_period, NULL); |
| 117 | if (FAILED(hr)) { |
| 118 | LOG(ERROR) << "IAudioClient::GetDevicePeriod failed. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 119 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 120 | } |
| 121 | audio_device_period_ = base::TimeDelta::FromMilliseconds( |
| 122 | device_period / kChannels / kHnsToMs); |
| 123 | |
| 124 | // Get the wave format. |
| 125 | hr = audio_client_->GetMixFormat(&wave_format_ex_); |
| 126 | if (FAILED(hr)) { |
| 127 | LOG(ERROR) << "Failed to get WAVEFORMATEX. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 128 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 129 | } |
| 130 | |
| 131 | // Set the wave format |
| 132 | switch (wave_format_ex_->wFormatTag) { |
| 133 | case WAVE_FORMAT_IEEE_FLOAT: |
| 134 | // Intentional fall-through. |
| 135 | case WAVE_FORMAT_PCM: |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 136 | if (!AudioCapturer::IsValidSampleRate(wave_format_ex_->nSamplesPerSec)) { |
| 137 | LOG(ERROR) << "Host sampling rate is neither 44.1 kHz nor 48 kHz."; |
| 138 | return false; |
| 139 | } |
| 140 | sampling_rate_ = static_cast<AudioPacket::SamplingRate>( |
| 141 | wave_format_ex_->nSamplesPerSec); |
| 142 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 143 | wave_format_ex_->wFormatTag = WAVE_FORMAT_PCM; |
| 144 | wave_format_ex_->nChannels = kChannels; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 145 | wave_format_ex_->wBitsPerSample = kBitsPerSample; |
| 146 | wave_format_ex_->nBlockAlign = kChannels * kBitsPerSample / kBitsPerByte; |
| 147 | wave_format_ex_->nAvgBytesPerSec = |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 148 | sampling_rate_ * kChannels * kBitsPerSample / kBitsPerByte; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 149 | break; |
| 150 | case WAVE_FORMAT_EXTENSIBLE: { |
| 151 | PWAVEFORMATEXTENSIBLE wave_format_extensible = |
| 152 | reinterpret_cast<WAVEFORMATEXTENSIBLE*>( |
| 153 | static_cast<WAVEFORMATEX*>(wave_format_ex_)); |
| 154 | if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, |
| 155 | wave_format_extensible->SubFormat)) { |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 156 | if (!AudioCapturer::IsValidSampleRate( |
| 157 | wave_format_extensible->Format.nSamplesPerSec)) { |
| 158 | LOG(ERROR) << "Host sampling rate is neither 44.1 kHz nor 48 kHz."; |
| 159 | return false; |
| 160 | } |
| 161 | sampling_rate_ = static_cast<AudioPacket::SamplingRate>( |
| 162 | wave_format_extensible->Format.nSamplesPerSec); |
| 163 | |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 164 | wave_format_extensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; |
| 165 | wave_format_extensible->Samples.wValidBitsPerSample = kBitsPerSample; |
| 166 | |
| 167 | wave_format_extensible->Format.nChannels = kChannels; |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 168 | wave_format_extensible->Format.nSamplesPerSec = sampling_rate_; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 169 | wave_format_extensible->Format.wBitsPerSample = kBitsPerSample; |
| 170 | wave_format_extensible->Format.nBlockAlign = |
| 171 | kChannels * kBitsPerSample / kBitsPerByte; |
| 172 | wave_format_extensible->Format.nAvgBytesPerSec = |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 173 | sampling_rate_ * kChannels * kBitsPerSample / kBitsPerByte; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 174 | } else { |
| 175 | LOG(ERROR) << "Failed to force 16-bit samples"; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 176 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 177 | } |
| 178 | break; |
| 179 | } |
| 180 | default: |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 181 | LOG(ERROR) << "Failed to force 16-bit PCM"; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 182 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 183 | } |
| 184 | |
| 185 | // Initialize the IAudioClient. |
| 186 | hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, |
| 187 | AUDCLNT_STREAMFLAGS_LOOPBACK, |
| 188 | 0, |
| 189 | 0, |
| 190 | wave_format_ex_, |
| 191 | NULL); |
| 192 | if (FAILED(hr)) { |
| 193 | LOG(ERROR) << "Failed to initialize IAudioClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 194 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 195 | } |
| 196 | |
| 197 | // Get an IAudioCaptureClient. |
| 198 | hr = audio_client_->GetService(__uuidof(IAudioCaptureClient), |
| 199 | audio_capture_client_.ReceiveVoid()); |
| 200 | if (FAILED(hr)) { |
| 201 | LOG(ERROR) << "Failed to get an IAudioCaptureClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 202 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 203 | } |
| 204 | |
| 205 | // Start the IAudioClient. |
| 206 | hr = audio_client_->Start(); |
| 207 | if (FAILED(hr)) { |
| 208 | LOG(ERROR) << "Failed to start IAudioClient. Error " << hr; |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 209 | return false; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 210 | } |
| 211 | |
| 212 | // Start capturing. |
| 213 | capture_timer_->Start(FROM_HERE, |
| 214 | audio_device_period_, |
| 215 | this, |
| 216 | &AudioCapturerWin::DoCapture); |
[email protected] | 339abeca | 2012-07-20 18:28:24 | [diff] [blame] | 217 | return true; |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | void AudioCapturerWin::Stop() { |
| 221 | DCHECK(thread_checker_.CalledOnValidThread()); |
| 222 | DCHECK(IsRunning()); |
| 223 | |
| 224 | capture_timer_.reset(); |
| 225 | mm_device_.Release(); |
| 226 | audio_client_.Release(); |
| 227 | audio_capture_client_.Release(); |
| 228 | wave_format_ex_.Reset(NULL); |
| 229 | com_initializer_.reset(); |
| 230 | |
| 231 | thread_checker_.DetachFromThread(); |
| 232 | } |
| 233 | |
| 234 | bool AudioCapturerWin::IsRunning() { |
| 235 | DCHECK(thread_checker_.CalledOnValidThread()); |
| 236 | return capture_timer_.get() != NULL; |
| 237 | } |
| 238 | |
| 239 | void AudioCapturerWin::DoCapture() { |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 240 | DCHECK(AudioCapturer::IsValidSampleRate(sampling_rate_)); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 241 | DCHECK(thread_checker_.CalledOnValidThread()); |
| 242 | DCHECK(IsRunning()); |
| 243 | |
| 244 | // Fetch all packets from the audio capture endpoint buffer. |
| 245 | while (true) { |
| 246 | UINT32 next_packet_size; |
| 247 | HRESULT hr = audio_capture_client_->GetNextPacketSize(&next_packet_size); |
| 248 | if (FAILED(hr)) { |
| 249 | LOG(ERROR) << "Failed to GetNextPacketSize. Error " << hr; |
| 250 | return; |
| 251 | } |
| 252 | |
| 253 | if (next_packet_size <= 0) { |
| 254 | return; |
| 255 | } |
| 256 | |
| 257 | BYTE* data; |
| 258 | UINT32 frames; |
| 259 | DWORD flags; |
| 260 | hr = audio_capture_client_->GetBuffer( |
| 261 | &data, &frames, &flags, NULL, NULL); |
| 262 | if (FAILED(hr)) { |
| 263 | LOG(ERROR) << "Failed to GetBuffer. Error " << hr; |
| 264 | return; |
| 265 | } |
| 266 | |
| 267 | scoped_ptr<AudioPacket> packet = scoped_ptr<AudioPacket>(new AudioPacket()); |
| 268 | packet->set_data(data, frames * wave_format_ex_->nBlockAlign); |
[email protected] | ca148891 | 2012-07-27 17:19:04 | [diff] [blame^] | 269 | packet->set_sampling_rate(sampling_rate_); |
[email protected] | c9bdcbd | 2012-07-19 23:35:54 | [diff] [blame] | 270 | |
| 271 | callback_.Run(packet.Pass()); |
| 272 | |
| 273 | hr = audio_capture_client_->ReleaseBuffer(frames); |
| 274 | if (FAILED(hr)) { |
| 275 | LOG(ERROR) << "Failed to ReleaseBuffer. Error " << hr; |
| 276 | return; |
| 277 | } |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | scoped_ptr<AudioCapturer> AudioCapturer::Create() { |
| 282 | return scoped_ptr<AudioCapturer>(new AudioCapturerWin()); |
| 283 | } |
| 284 | |
| 285 | } // namespace remoting |