| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/host/audio_capturer_win.h" |
| |
| #include <avrt.h> |
| #include <mmreg.h> |
| #include <mmsystem.h> |
| #include <objbase.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <windows.h> |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/synchronization/lock.h" |
| #include "remoting/host/win/default_audio_device_change_detector.h" |
| |
| namespace { |
| const int kBytesPerSample = 2; |
| const int kBitsPerSample = kBytesPerSample * 8; |
| // Conversion factor from 100ns to 1ms. |
| const int k100nsPerMillisecond = 10000; |
| |
| // Tolerance for catching packets of silence. If all samples have absolute |
| // value less than this threshold, the packet will be counted as a packet of |
| // silence. A value of 2 was chosen, because Windows can give samples of 1 and |
| // -1, even when no audio is playing. |
| const int kSilenceThreshold = 2; |
| |
| // Lower bound for timer intervals, in milliseconds. |
| const int kMinTimerInterval = 30; |
| |
| // Upper bound for the timer precision error, in milliseconds. |
| // Timers are supposed to be accurate to 20ms, so we use 30ms to be safe. |
| const int kMaxExpectedTimerLag = 30; |
| |
| } // namespace |
| |
| namespace remoting { |
| |
| AudioCapturerWin::AudioCapturerWin() |
| : sampling_rate_(AudioPacket::SAMPLING_RATE_INVALID), |
| volume_filter_(kSilenceThreshold), |
| last_capture_error_(S_OK) { |
| thread_checker_.DetachFromThread(); |
| } |
| |
| AudioCapturerWin::~AudioCapturerWin() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| Deinitialize(); |
| } |
| |
| bool AudioCapturerWin::Start(const PacketCapturedCallback& callback) { |
| callback_ = callback; |
| |
| if (!Initialize()) { |
| return false; |
| } |
| |
| // Initialize the capture timer and start capturing. Note, this timer won't |
| // be reset or restarted in ResetAndInitialize() function. Which means we |
| // expect the audio_device_period_ is a system wide configuration, it would |
| // not be changed with the default audio device. |
| capture_timer_.reset(new base::RepeatingTimer()); |
| capture_timer_->Start(FROM_HERE, audio_device_period_, this, |
| &AudioCapturerWin::DoCapture); |
| return true; |
| } |
| |
| bool AudioCapturerWin::ResetAndInitialize() { |
| Deinitialize(); |
| if (!Initialize()) { |
| Deinitialize(); |
| return false; |
| } |
| return true; |
| } |
| |
| void AudioCapturerWin::Deinitialize() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| wave_format_ex_.Reset(nullptr); |
| default_device_detector_.reset(); |
| audio_capture_client_.Reset(); |
| if (audio_client_) { |
| audio_client_->Stop(); |
| } |
| audio_client_.Reset(); |
| mm_device_.Reset(); |
| } |
| |
| bool AudioCapturerWin::Initialize() { |
| DCHECK(!audio_capture_client_.Get()); |
| DCHECK(!audio_client_.Get()); |
| DCHECK(!mm_device_.Get()); |
| DCHECK(static_cast<PWAVEFORMATEX>(wave_format_ex_) == nullptr); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| HRESULT hr = S_OK; |
| Microsoft::WRL::ComPtr<IMMDeviceEnumerator> mm_device_enumerator; |
| hr = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, |
| IID_PPV_ARGS(&mm_device_enumerator)); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to create IMMDeviceEnumerator. Error " << hr; |
| return false; |
| } |
| |
| default_device_detector_.reset( |
| new DefaultAudioDeviceChangeDetector(mm_device_enumerator)); |
| |
| // Get the audio endpoint. |
| hr = mm_device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, |
| mm_device_.GetAddressOf()); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to get IMMDevice. Error " << hr; |
| return false; |
| } |
| |
| // Get an audio client. |
| hr = mm_device_->Activate(__uuidof(IAudioClient), |
| CLSCTX_ALL, |
| nullptr, |
| &audio_client_); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to get an IAudioClient. Error " << hr; |
| return false; |
| } |
| |
| REFERENCE_TIME device_period; |
| hr = audio_client_->GetDevicePeriod(&device_period, nullptr); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "IAudioClient::GetDevicePeriod failed. Error " << hr; |
| return false; |
| } |
| // We round up, if |device_period| / |k100nsPerMillisecond| |
| // is not a whole number. |
| int device_period_in_milliseconds = |
| 1 + ((device_period - 1) / k100nsPerMillisecond); |
| audio_device_period_ = base::TimeDelta::FromMilliseconds( |
| std::max(device_period_in_milliseconds, kMinTimerInterval)); |
| |
| // Get the wave format. |
| hr = audio_client_->GetMixFormat(&wave_format_ex_); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to get WAVEFORMATEX. Error " << hr; |
| return false; |
| } |
| |
| if (wave_format_ex_->wFormatTag != WAVE_FORMAT_IEEE_FLOAT && |
| wave_format_ex_->wFormatTag != WAVE_FORMAT_PCM && |
| wave_format_ex_->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { |
| LOG(ERROR) << "Failed to force 16-bit PCM"; |
| return false; |
| } |
| |
| if (!AudioCapturer::IsValidSampleRate(wave_format_ex_->nSamplesPerSec)) { |
| LOG(ERROR) << "Host sampling rate is neither 44.1 kHz nor 48 kHz. " |
| << wave_format_ex_->nSamplesPerSec; |
| return false; |
| } |
| |
| // We support from mono to 7.1. This check should be consistent with |
| // AudioPacket::Channels. |
| if (wave_format_ex_->nChannels > 8 || wave_format_ex_->nChannels <= 0) { |
| LOG(ERROR) << "Unsupported channels " << wave_format_ex_->nChannels; |
| return false; |
| } |
| |
| sampling_rate_ = static_cast<AudioPacket::SamplingRate>( |
| wave_format_ex_->nSamplesPerSec); |
| |
| wave_format_ex_->wBitsPerSample = kBitsPerSample; |
| wave_format_ex_->nBlockAlign = wave_format_ex_->nChannels * kBytesPerSample; |
| wave_format_ex_->nAvgBytesPerSec = |
| sampling_rate_ * wave_format_ex_->nBlockAlign; |
| |
| if (wave_format_ex_->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { |
| PWAVEFORMATEXTENSIBLE wave_format_extensible = |
| reinterpret_cast<WAVEFORMATEXTENSIBLE*>( |
| static_cast<WAVEFORMATEX*>(wave_format_ex_)); |
| if (!IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, |
| wave_format_extensible->SubFormat) && |
| !IsEqualGUID(KSDATAFORMAT_SUBTYPE_PCM, |
| wave_format_extensible->SubFormat)) { |
| LOG(ERROR) << "Failed to force 16-bit samples"; |
| return false; |
| } |
| |
| wave_format_extensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; |
| wave_format_extensible->Samples.wValidBitsPerSample = kBitsPerSample; |
| } else { |
| wave_format_ex_->wFormatTag = WAVE_FORMAT_PCM; |
| } |
| |
| // Initialize the IAudioClient. |
| hr = audio_client_->Initialize( |
| AUDCLNT_SHAREMODE_SHARED, |
| AUDCLNT_STREAMFLAGS_LOOPBACK, |
| (kMaxExpectedTimerLag + audio_device_period_.InMilliseconds()) * |
| k100nsPerMillisecond, |
| 0, |
| wave_format_ex_, |
| nullptr); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to initialize IAudioClient. Error " << hr; |
| return false; |
| } |
| |
| // Get an IAudioCaptureClient. |
| hr = audio_client_->GetService(IID_PPV_ARGS(&audio_capture_client_)); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to get an IAudioCaptureClient. Error " << hr; |
| return false; |
| } |
| |
| // Start the IAudioClient. |
| hr = audio_client_->Start(); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "Failed to start IAudioClient. Error " << hr; |
| return false; |
| } |
| |
| volume_filter_.ActivateBy(mm_device_.Get()); |
| volume_filter_.Initialize(sampling_rate_, wave_format_ex_->nChannels); |
| |
| return true; |
| } |
| |
| bool AudioCapturerWin::is_initialized() const { |
| // All Com components should be initialized / deinitialized together. |
| return !!audio_client_; |
| } |
| |
| void AudioCapturerWin::DoCapture() { |
| DCHECK(AudioCapturer::IsValidSampleRate(sampling_rate_)); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!is_initialized() || default_device_detector_->GetAndReset()) { |
| if (!ResetAndInitialize()) { |
| // Initialization failed, we should wait for next DoCapture call. |
| return; |
| } |
| } |
| |
| // Fetch all packets from the audio capture endpoint buffer. |
| HRESULT hr = S_OK; |
| while (true) { |
| UINT32 next_packet_size; |
| HRESULT hr = audio_capture_client_->GetNextPacketSize(&next_packet_size); |
| if (FAILED(hr)) |
| break; |
| |
| if (next_packet_size <= 0) { |
| return; |
| } |
| |
| BYTE* data; |
| UINT32 frames; |
| DWORD flags; |
| hr = audio_capture_client_->GetBuffer(&data, &frames, &flags, nullptr, |
| nullptr); |
| if (FAILED(hr)) |
| break; |
| |
| if (volume_filter_.Apply(reinterpret_cast<int16_t*>(data), frames)) { |
| std::unique_ptr<AudioPacket> packet(new AudioPacket()); |
| packet->add_data(data, frames * wave_format_ex_->nBlockAlign); |
| packet->set_encoding(AudioPacket::ENCODING_RAW); |
| packet->set_sampling_rate(sampling_rate_); |
| packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2); |
| // Only the count of channels is taken into account now, we should also |
| // consider dwChannelMask. |
| // TODO(zijiehe): Convert dwChannelMask to layout and pass it to |
| // AudioPump. So the stream can be downmixed properly with both number and |
| // layouts of speakers. |
| packet->set_channels(static_cast<AudioPacket::Channels>( |
| wave_format_ex_->nChannels)); |
| |
| callback_.Run(std::move(packet)); |
| } |
| |
| hr = audio_capture_client_->ReleaseBuffer(frames); |
| if (FAILED(hr)) |
| break; |
| } |
| |
| // There is nothing to capture if the audio endpoint device has been unplugged |
| // or disabled. |
| if (hr == AUDCLNT_E_DEVICE_INVALIDATED) |
| return; |
| |
| // Avoid reporting the same error multiple times. |
| if (FAILED(hr) && hr != last_capture_error_) { |
| last_capture_error_ = hr; |
| LOG(ERROR) << "Failed to capture an audio packet: 0x" |
| << std::hex << hr << std::dec << "."; |
| } |
| } |
| |
| bool AudioCapturer::IsSupported() { |
| return true; |
| } |
| |
| std::unique_ptr<AudioCapturer> AudioCapturer::Create() { |
| return base::WrapUnique(new AudioCapturerWin()); |
| } |
| |
| } // namespace remoting |