Commit 1d62ab1f authored by Henrik Grunell's avatar Henrik Grunell Committed by Commit Bot

Add Windows input audio voice capture DMO for voice processing.

Controlled by existing origin trial.

Bug: 845187
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: I43e4e0d9f303086b50e9dd57ae547bfdcd79f204
Reviewed-on: https://chromium-review.googlesource.com/1065918
Commit-Queue: Henrik Grunell <grunell@chromium.org>
Reviewed-by: default avatarTommi <tommi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#560677}
parent 979ca1f9
......@@ -222,9 +222,13 @@ source_set("audio") {
]
libs += [
"dmoguids.lib",
"dxguid.lib",
"msdmo.lib",
"setupapi.lib",
"strmiids.lib",
"winmm.lib",
"wmcodecdspuuid.lib",
]
}
......
......@@ -33,6 +33,8 @@ class AudioOutputDispatcher;
// AudioManagerBase provides AudioManager functions common for all platforms.
class MEDIA_EXPORT AudioManagerBase : public AudioManager {
public:
enum class VoiceProcessingMode { kDisabled = 0, kEnabled = 1 };
~AudioManagerBase() override;
AudioOutputStream* MakeAudioOutputStream(
......
......@@ -20,7 +20,6 @@
#include "base/sys_info.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/audio/mac/scoped_audio_unit.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_timestamp_helper.h"
......@@ -165,7 +164,7 @@ AUAudioInputStream::AUAudioInputStream(
const AudioParameters& input_params,
AudioDeviceID audio_device_id,
const AudioManager::LogCallback& log_callback,
VoiceProcessingMode voice_processing_mode)
AudioManagerBase::VoiceProcessingMode voice_processing_mode)
: manager_(manager),
input_params_(input_params),
number_of_frames_provided_(0),
......@@ -184,7 +183,7 @@ AUAudioInputStream::AUAudioInputStream(
audio_unit_render_has_worked_(false),
noise_reduction_suppressed_(false),
use_voice_processing_(voice_processing_mode ==
VoiceProcessingMode::ENABLED),
AudioManagerBase::VoiceProcessingMode::kEnabled),
last_sample_time_(0.0),
last_number_of_frames_(0),
total_lost_frames_(0),
......
......@@ -50,26 +50,23 @@
#include "base/timer/timer.h"
#include "media/audio/agc_audio_stream.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/audio_block_fifo.h"
#include "media/base/audio_parameters.h"
namespace media {
class AudioManagerMac;
class MEDIA_EXPORT AUAudioInputStream
: public AgcAudioStream<AudioInputStream> {
public:
enum class VoiceProcessingMode { DISABLED = 0, ENABLED = 1 };
// The ctor takes all the usual parameters, plus |manager| which is the
// the audio manager who is creating this object.
AUAudioInputStream(AudioManagerMac* manager,
AUAudioInputStream(
AudioManagerMac* manager,
const AudioParameters& input_params,
AudioDeviceID audio_device_id,
const AudioManager::LogCallback& log_callback,
VoiceProcessingMode voice_processing_mode);
AudioManagerBase::VoiceProcessingMode voice_processing_mode);
// The dtor is typically called by the AudioManager only and it is usually
// triggered by calling AudioInputStream::Close().
~AUAudioInputStream() override;
......
......@@ -795,11 +795,10 @@ AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
return nullptr;
}
using VoiceProcessingMode = AUAudioInputStream::VoiceProcessingMode;
VoiceProcessingMode voice_processing_mode =
(params.effects() & AudioParameters::ECHO_CANCELLER)
? VoiceProcessingMode::ENABLED
: VoiceProcessingMode::DISABLED;
? VoiceProcessingMode::kEnabled
: VoiceProcessingMode::kDisabled;
auto* stream = new AUAudioInputStream(this, params, audio_device_id,
log_callback, voice_processing_mode);
......
......@@ -4,7 +4,11 @@
#include "media/audio/win/audio_low_latency_input_win.h"
#include <audiopolicy.h>
#include <mediaobj.h>
#include <objbase.h>
#include <uuids.h>
#include <wmcodecdsp.h>
#include <algorithm>
#include <cmath>
......@@ -18,7 +22,6 @@
#include "base/trace_event/trace_event.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_features.h"
#include "media/audio/win/audio_manager_win.h"
#include "media/audio/win/avrt_wrapper_win.h"
#include "media/audio/win/core_audio_util_win.h"
#include "media/base/audio_block_fifo.h"
......@@ -67,18 +70,92 @@ bool IsSupportedFormatForConversion(const WAVEFORMATEX& format) {
return true;
}
// Implementation of IMediaBuffer, as required for
// IMediaObject::ProcessOutput(). After consuming data provided by
// ProcessOutput(), call SetLength() to update the buffer availability.
// Example implementation:
// http://msdn.microsoft.com/en-us/library/dd376684(v=vs.85).aspx
class MediaBufferImpl : public IMediaBuffer {
public:
explicit MediaBufferImpl(DWORD max_length)
: data_(new BYTE[max_length]), max_length_(max_length) {}
// IMediaBuffer implementation.
STDMETHOD(GetBufferAndLength)(BYTE** buffer, DWORD* length) {
if (!buffer || !length)
return E_POINTER;
*buffer = data_.get();
*length = length_;
return S_OK;
}
STDMETHOD(GetMaxLength)(DWORD* max_length) {
if (!max_length)
return E_POINTER;
*max_length = max_length_;
return S_OK;
}
STDMETHOD(SetLength)(DWORD length) {
if (length > max_length_)
return E_INVALIDARG;
length_ = length;
return S_OK;
}
// IUnknown implementation.
STDMETHOD_(ULONG, AddRef)() { return InterlockedIncrement(&ref_count_); }
STDMETHOD(QueryInterface)(REFIID riid, void** object) {
if (!object)
return E_POINTER;
if (riid != IID_IMediaBuffer && riid != IID_IUnknown)
return E_NOINTERFACE;
*object = static_cast<IMediaBuffer*>(this);
AddRef();
return S_OK;
}
STDMETHOD_(ULONG, Release)() {
LONG ref_count = InterlockedDecrement(&ref_count_);
if (ref_count == 0)
delete this;
return ref_count;
}
private:
virtual ~MediaBufferImpl() {}
std::unique_ptr<BYTE[]> data_;
DWORD length_ = 0;
const DWORD max_length_;
LONG ref_count_ = 0;
};
} // namespace
WASAPIAudioInputStream::WASAPIAudioInputStream(
AudioManagerWin* manager,
const AudioParameters& params,
const std::string& device_id,
const AudioManager::LogCallback& log_callback)
: manager_(manager), device_id_(device_id), log_callback_(log_callback) {
const AudioManager::LogCallback& log_callback,
AudioManagerBase::VoiceProcessingMode voice_processing_mode)
: manager_(manager),
device_id_(device_id),
log_callback_(log_callback),
use_voice_processing_(voice_processing_mode ==
AudioManagerBase::VoiceProcessingMode::kEnabled) {
DCHECK(manager_);
DCHECK(!device_id_.empty());
DCHECK(!log_callback_.is_null());
DVLOG_IF(1, use_voice_processing_) << "Using Windows voice capture DSP DMO.";
// Load the Avrt DLL if not already loaded. Required to support MMCSS.
bool avrt_init = avrt::Initialize();
DCHECK(avrt_init) << "Failed to load the Avrt.dll";
......@@ -101,13 +178,13 @@ WASAPIAudioInputStream::WASAPIAudioInputStream(
input_format_ = output_format_;
// Size in bytes of each audio frame.
frame_size_ = input_format_.nBlockAlign;
frame_size_bytes_ = input_format_.nBlockAlign;
// Store size of audio packets which we expect to get from the audio
// endpoint device in each capture event.
packet_size_bytes_ = params.GetBytesPerBuffer(kSampleFormat);
packet_size_frames_ = packet_size_bytes_ / input_format_.nBlockAlign;
DVLOG(1) << "Number of bytes per audio frame : " << frame_size_;
DVLOG(1) << "Number of bytes per audio frame : " << frame_size_bytes_;
DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_;
// All events are auto-reset events and non-signaled initially.
......@@ -145,6 +222,16 @@ bool WASAPIAudioInputStream::Open() {
return false;
}
// If voice processing is enabled, initialize the DMO that is used for it. The
// remainder of the function initializes an audio capture client (the normal
// case). Either the DMO or the capture client is used.
// TODO(grunell): Refactor out the audio capture client initialization to its
// own function.
if (use_voice_processing_) {
opened_ = InitializeDmo();
return opened_;
}
// Obtain an IAudioClient interface which enables us to create and initialize
// an audio stream between an audio application and the audio engine.
hr = endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
......@@ -192,6 +279,15 @@ void WASAPIAudioInputStream::Start(AudioInputCallback* callback) {
if (started_)
return;
// TODO(grunell): Refactor the |use_voice_processing_| conditions in this
// function to clean up the code.
if (use_voice_processing_) {
// Pre-fill render buffer with silence.
if (!CoreAudioUtil::FillRenderEndpointBufferWithSilence(
audio_client_for_render_.Get(), audio_render_client_.Get())) {
DLOG(WARNING) << "Failed to pre-fill render buffer with silence.";
}
} else {
if (device_id_ == AudioDeviceDescription::kLoopbackWithMuteDeviceId &&
system_audio_volume_) {
BOOL muted = false;
......@@ -205,6 +301,7 @@ void WASAPIAudioInputStream::Start(AudioInputCallback* callback) {
mute_done_ = true;
}
}
}
DCHECK(!sink_);
sink_ = callback;
......@@ -221,12 +318,21 @@ void WASAPIAudioInputStream::Start(AudioInputCallback* callback) {
base::SimpleThread::Options(base::ThreadPriority::REALTIME_AUDIO)));
capture_thread_->Start();
HRESULT hr = E_FAIL;
if (use_voice_processing_) {
hr = audio_client_for_render_->Start();
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to start output streaming: " << std::hex << hr
<< ", proceeding without rendering.";
}
} else {
// Start streaming data between the endpoint buffer and the audio engine.
HRESULT hr = audio_client_->Start();
hr = audio_client_->Start();
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to start input streaming.";
log_callback_.Run(base::StringPrintf(
"WASAPIAIS::Start: Failed to start audio client, hresult = %#lx", hr));
"WASAPIAIS::Start: Failed to start audio client, hresult = %#lx",
hr));
}
if (SUCCEEDED(hr) && audio_render_client_for_loopback_.Get()) {
......@@ -237,6 +343,7 @@ void WASAPIAudioInputStream::Start(AudioInputCallback* callback) {
"hresult = %#lx",
hr));
}
}
started_ = SUCCEEDED(hr);
}
......@@ -266,10 +373,21 @@ void WASAPIAudioInputStream::Stop() {
SetEvent(stop_capture_event_.Get());
}
// TODO(grunell): Refactor the |use_voice_processing_| conditions in this
// function to clean up the code.
if (use_voice_processing_) {
// Stop the render audio streaming. The input streaming needs no explicit
// stopping.
HRESULT hr = audio_client_for_render_->Stop();
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to stop output streaming.";
}
} else {
// Stop the input audio streaming.
HRESULT hr = audio_client_->Stop();
if (FAILED(hr)) {
LOG(ERROR) << "Failed to stop input streaming.";
DLOG(ERROR) << "Failed to stop input streaming.";
}
}
// Wait until the thread completes and perform cleanup.
......@@ -279,6 +397,12 @@ void WASAPIAudioInputStream::Stop() {
capture_thread_.reset();
}
if (use_voice_processing_) {
HRESULT hr = voice_capture_dmo_->FreeStreamingResources();
if (FAILED(hr))
DLOG(ERROR) << "Failed to free dmo resources.";
}
started_ = false;
sink_ = NULL;
}
......@@ -399,8 +523,8 @@ void WASAPIAudioInputStream::Run() {
// be able to buffer up data in cases where a conversion requires two audio
// buffers (and we need to be able to write to the third one).
size_t capture_buffer_size =
std::max(2 * endpoint_buffer_size_frames_ * frame_size_,
2 * packet_size_frames_ * frame_size_);
std::max(2 * endpoint_buffer_size_frames_ * frame_size_bytes_,
2 * packet_size_frames_ * frame_size_bytes_);
int buffers_required = capture_buffer_size / packet_size_bytes_;
if (converter_ && imperfect_buffer_size_conversion_)
++buffers_required;
......@@ -411,48 +535,75 @@ void WASAPIAudioInputStream::Run() {
DVLOG(1) << "AudioBlockFifo buffer count: " << buffers_required;
bool recording = true;
bool error = false;
bool success =
use_voice_processing_ ? RunWithDmo() : RunWithAudioCaptureClient();
if (!success) {
// TODO(henrika): perhaps it worth improving the cleanup here by e.g.
// stopping the audio client, joining the thread etc.?
NOTREACHED() << "WASAPI capturing failed with error code "
<< GetLastError();
}
// Disable MMCSS.
if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) {
PLOG(WARNING) << "Failed to disable MMCSS";
}
fifo_.reset();
}
bool WASAPIAudioInputStream::RunWithAudioCaptureClient() {
HANDLE wait_array[2] = {stop_capture_event_.Get(),
audio_samples_ready_event_.Get()};
while (recording && !error) {
while (true) {
// Wait for a close-down event or a new capture event.
DWORD wait_result = WaitForMultipleObjects(2, wait_array, FALSE, INFINITE);
switch (wait_result) {
case WAIT_OBJECT_0 + 0:
// |stop_capture_event_| has been set.
recording = false;
break;
return true;
case WAIT_OBJECT_0 + 1:
// |audio_samples_ready_event_| has been set.
PullCaptureDataAndPushToSink();
break;
case WAIT_FAILED:
default:
error = true;
break;
return false;
}
}
if (recording && error) {
// TODO(henrika): perhaps it worth improving the cleanup here by e.g.
// stopping the audio client, joining the thread etc.?
NOTREACHED() << "WASAPI capturing failed with error code "
<< GetLastError();
}
return false;
}
// Disable MMCSS.
if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) {
PLOG(WARNING) << "Failed to disable MMCSS";
bool WASAPIAudioInputStream::RunWithDmo() {
while (true) {
// Poll every 5 ms, or wake up on capture stop signal.
DWORD wait_result = WaitForSingleObject(stop_capture_event_.Get(), 5);
switch (wait_result) {
case WAIT_OBJECT_0:
// |stop_capture_event_| has been set.
return true;
case WAIT_TIMEOUT:
PullDmoCaptureDataAndPushToSink();
if (!CoreAudioUtil::FillRenderEndpointBufferWithSilence(
audio_client_for_render_.Get(), audio_render_client_.Get())) {
DLOG(WARNING) << "Failed to fill render buffer with silence.";
}
break;
case WAIT_FAILED:
default:
return false;
}
}
fifo_.reset();
return false;
}
void WASAPIAudioInputStream::PullCaptureDataAndPushToSink() {
TRACE_EVENT1("audio", "WASAPIAudioInputStream::Run_0", "sample rate",
input_format_.nSamplesPerSec);
TRACE_EVENT1("audio", "WASAPIAudioInputStream::PullCaptureDataAndPushToSink",
"sample rate", input_format_.nSamplesPerSec);
// Pull data from the capture endpoint buffer until it's empty or an error
// occurs.
......@@ -566,6 +717,106 @@ void WASAPIAudioInputStream::PullCaptureDataAndPushToSink() {
} // while (true)
}
void WASAPIAudioInputStream::PullDmoCaptureDataAndPushToSink() {
TRACE_EVENT1("audio",
"WASAPIAudioInputStream::PullDmoCaptureDataAndPushToSink",
"sample rate", input_format_.nSamplesPerSec);
// Pull data from the capture endpoint buffer until it's empty or an error
// occurs.
while (true) {
DWORD status = 0;
DMO_OUTPUT_DATA_BUFFER data_buffer = {0};
data_buffer.pBuffer = media_buffer_.Get();
// Get processed capture data from the DMO.
HRESULT hr =
voice_capture_dmo_->ProcessOutput(0, // dwFlags
1, // cOutputBufferCount
&data_buffer,
&status); // Must be ignored.
if (FAILED(hr)) {
DLOG(ERROR) << "DMO ProcessOutput failed, hr = 0x" << std::hex << hr;
break;
}
BYTE* data;
ULONG data_length = 0;
// Get a pointer to the data buffer. This should be valid until the next
// call to ProcessOutput.
hr = media_buffer_->GetBufferAndLength(&data, &data_length);
if (FAILED(hr)) {
DLOG(ERROR) << "Could not get buffer, hr = 0x" << std::hex << hr;
break;
}
if (data_length > 0) {
const int samples_produced = data_length / frame_size_bytes_;
base::TimeTicks capture_time;
if (data_buffer.dwStatus & DMO_OUTPUT_DATA_BUFFERF_TIME &&
data_buffer.rtTimestamp > 0) {
// See conversion notes on |capture_time_100ns| in
// PullCaptureDataAndPushToSink().
capture_time +=
base::TimeDelta::FromMicroseconds(data_buffer.rtTimestamp / 10.0);
} else {
// We may not get the timestamp from ProcessOutput(), fall back on
// current timestamp.
capture_time = base::TimeTicks::Now();
}
// Adjust |capture_time| for the FIFO before pushing.
capture_time -= AudioTimestampHelper::FramesToTime(
fifo_->GetAvailableFrames(), input_format_.nSamplesPerSec);
fifo_->Push(data, samples_produced, input_format_.wBitsPerSample / 8);
// Reset length to indicate buffer availability.
hr = media_buffer_->SetLength(0);
if (FAILED(hr))
DLOG(ERROR) << "Could not reset length, hr = 0x" << std::hex << hr;
// Get a cached AGC volume level which is updated once every second on the
// audio manager thread. Note that, |volume| is also updated each time
// SetVolume() is called through IPC by the render-side AGC.
double volume = 0.0;
GetAgcVolume(&volume);
while (fifo_->available_blocks()) {
if (converter_) {
if (imperfect_buffer_size_conversion_ &&
fifo_->available_blocks() == 1) {
// Special case. We need to buffer up more audio before we can
// convert or else we'll suffer an underrun.
// TODO(grunell): Verify this is really true.
break;
}
converter_->Convert(convert_bus_.get());
sink_->OnData(convert_bus_.get(), capture_time, volume);
// Move the capture time forward for each vended block.
capture_time += AudioTimestampHelper::FramesToTime(
convert_bus_->frames(), output_format_.nSamplesPerSec);
} else {
sink_->OnData(fifo_->Consume(), capture_time, volume);
// Move the capture time forward for each vended block.
capture_time += AudioTimestampHelper::FramesToTime(
packet_size_frames_, input_format_.nSamplesPerSec);
}
}
} // if (data_length > 0)
if (!(data_buffer.dwStatus & DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE)) {
// The DMO cannot currently produce more data. This is the normal case;
// otherwise it means the DMO had more than 10 ms of data available and
// ProcessOutput should be called again.
break;
}
} // while (true)
}
void WASAPIAudioInputStream::HandleError(HRESULT err) {
NOTREACHED() << "Error code: " << err;
if (sink_)
......@@ -733,13 +984,28 @@ bool WASAPIAudioInputStream::DesiredFormatIsSupported(HRESULT* hr) {
<< "\nblock align: " << input_format_.nBlockAlign
<< "\navg bytes per sec: " << input_format_.nAvgBytesPerSec;
SetupConverterAndStoreFormatInfo();
// Indicate that we're good to go with a close match.
hresult = S_OK;
}
}
// At this point, |hresult| == S_OK if the desired format is supported. If
// |hresult| == S_FALSE, the OS supports a closest match but we don't support
// conversion to it. Thus, SUCCEEDED() or FAILED() can't be used to determine
// if the desired format is supported.
*hr = hresult;
return (hresult == S_OK);
}
void WASAPIAudioInputStream::SetupConverterAndStoreFormatInfo() {
// Ideally, we want a 1:1 ratio between the buffers we get and the buffers
// we give to OnData so that each buffer we receive from the OS can be
// directly converted to a buffer that matches with what was asked for.
const double buffer_ratio = output_format_.nSamplesPerSec /
static_cast<double>(packet_size_frames_);
double new_frames_per_buffer =
input_format_.nSamplesPerSec / buffer_ratio;
const double buffer_ratio =
output_format_.nSamplesPerSec / static_cast<double>(packet_size_frames_);
double new_frames_per_buffer = input_format_.nSamplesPerSec / buffer_ratio;
const auto input_layout = GuessChannelLayout(input_format_.nChannels);
DCHECK_NE(CHANNEL_LAYOUT_UNSUPPORTED, input_layout);
......@@ -764,24 +1030,12 @@ bool WASAPIAudioInputStream::DesiredFormatIsSupported(HRESULT* hr) {
static_cast<int>(new_frames_per_buffer) * input_format_.nBlockAlign;
packet_size_frames_ = new_bytes_per_buffer / input_format_.nBlockAlign;
packet_size_bytes_ = new_bytes_per_buffer;
frame_size_ = input_format_.nBlockAlign;
frame_size_bytes_ = input_format_.nBlockAlign;
imperfect_buffer_size_conversion_ =
std::modf(new_frames_per_buffer, &new_frames_per_buffer) != 0.0;
DVLOG_IF(1, imperfect_buffer_size_conversion_)
<< "Audio capture data conversion: Need to inject fifo";
// Indicate that we're good to go with a close match.
hresult = S_OK;
}
}
// At this point, |hresult| == S_OK if the desired format is supported. If
// |hresult| == S_FALSE, the OS supports a closest match but we don't support
// conversion to it. Thus, SUCCEEDED() or FAILED() can't be used to determine
// if the desired format is supported.
*hr = hresult;
return (hresult == S_OK);
}
HRESULT WASAPIAudioInputStream::InitializeAudioEngine() {
......@@ -984,6 +1238,211 @@ void WASAPIAudioInputStream::MaybeReportFormatRelatedInitError(
FormatRelatedInitError::kCount);
}
bool WASAPIAudioInputStream::InitializeDmo() {
HRESULT hr = ::CoCreateInstance(CLSID_CWMAudioAEC, NULL, CLSCTX_INPROC_SERVER,
IID_IMediaObject, &voice_capture_dmo_);
if (FAILED(hr)) {
DLOG(ERROR) << "Creating DMO failed.";
return false;
}
if (!SetDmoProperties())
return false;
if (!SetDmoFormat())
return false;
hr = voice_capture_dmo_->AllocateStreamingResources();
if (FAILED(hr)) {
DLOG(ERROR) << "Allocating DMO resources failed.";
return false;
}
SetupConverterAndStoreFormatInfo();
media_buffer_ =
new MediaBufferImpl(endpoint_buffer_size_frames_ * frame_size_bytes_);
if (!CreateDummyRenderClientsForDmo())
return false;
// Get volume interface.
Microsoft::WRL::ComPtr<IAudioSessionManager> audio_session_manager;
hr = endpoint_device_->Activate(__uuidof(IAudioSessionManager),
CLSCTX_INPROC_SERVER, NULL,
&audio_session_manager);
if (FAILED(hr)) {
DLOG(ERROR) << "Obtaining audio session manager failed.";
return false;
}
hr = audio_session_manager->GetSimpleAudioVolume(
NULL, // AudioSessionGuid. NULL for default session.
FALSE, // CrossProcessSession.
&simple_audio_volume_);
if (FAILED(hr)) {
DLOG(ERROR) << "Obtaining audio volume interface failed.";
return false;
}
return true;
}
bool WASAPIAudioInputStream::SetDmoProperties() {
Microsoft::WRL::ComPtr<IPropertyStore> ps;
HRESULT hr = voice_capture_dmo_->QueryInterface(IID_IPropertyStore, &ps);
if (FAILED(hr) || !ps) {
DLOG(ERROR) << "Getting DMO property store failed.";
return false;
}
// Find the input device index if a non-default device is used.
// The default device is specified with -1. The default communications device
// cannot be specified, so we need to find the index for that underlying
// device.
WORD input_device_index = -1;
if (!AudioDeviceDescription::IsDefaultDevice(device_id_)) {
hr = CoreAudioUtil::GetDeviceCollectionIndex(
AudioDeviceDescription::IsCommunicationsDevice(device_id_)
? CoreAudioUtil::GetCommunicationsInputDeviceID()
: device_id_,
eCapture, &input_device_index);
if (FAILED(hr) || hr == S_FALSE)
return false;
}
// Set devices.
// TODO(grunell): Set output device to other than default.
WORD output_device_index = -1;
LONG device_index_value =
(static_cast<ULONG>(output_device_index) << 16) +
(static_cast<ULONG>(input_device_index) & 0x0000ffff);
if (FAILED(CoreAudioUtil::SetVtI4Property(
ps.Get(), MFPKEY_WMAAECMA_DEVICE_INDEXES, device_index_value))) {
DLOG(ERROR) << "Setting device indices failed.";
return false;
}
// Set DMO mode to AEC only.
if (FAILED(CoreAudioUtil::SetVtI4Property(
ps.Get(), MFPKEY_WMAAECMA_SYSTEM_MODE, SINGLE_CHANNEL_AEC))) {
DLOG(ERROR) << "Setting DMO system mode failed.";
return false;
}
// Enable the feature mode. This lets us override the default processing
// settings below.
if (FAILED(CoreAudioUtil::SetBoolProperty(
ps.Get(), MFPKEY_WMAAECMA_FEATURE_MODE, VARIANT_TRUE))) {
DLOG(ERROR) << "Setting DMO feature mode failed.";
return false;
}
// Disable analog AGC (default enabled).
if (FAILED(CoreAudioUtil::SetBoolProperty(
ps.Get(), MFPKEY_WMAAECMA_MIC_GAIN_BOUNDER, VARIANT_FALSE))) {
DLOG(ERROR) << "Setting DMO mic gain bounder failed.";
return false;
}
// Disable noise suppression (default enabled).
if (FAILED(CoreAudioUtil::SetVtI4Property(ps.Get(), MFPKEY_WMAAECMA_FEATR_NS,
0))) {
DLOG(ERROR) << "Disabling DMO NS failed.";
return false;
}
return true;
}
bool WASAPIAudioInputStream::SetDmoFormat() {
DMO_MEDIA_TYPE mt; // Media type.
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_PCM;
mt.lSampleSize = 0;
mt.bFixedSizeSamples = TRUE;
mt.bTemporalCompression = FALSE;
mt.formattype = FORMAT_WaveFormatEx;
HRESULT hr = MoInitMediaType(&mt, sizeof(WAVEFORMATEX));
if (FAILED(hr)) {
DLOG(ERROR) << "Init media type for DMO failed.";
return false;
}
WAVEFORMATEX* dmo_output_format =
reinterpret_cast<WAVEFORMATEX*>(mt.pbFormat);
dmo_output_format->wFormatTag = WAVE_FORMAT_PCM;
dmo_output_format->nChannels = 1;
dmo_output_format->nSamplesPerSec = 16000;
dmo_output_format->nAvgBytesPerSec = 32000;
dmo_output_format->nBlockAlign = 2;
dmo_output_format->wBitsPerSample = 16;
dmo_output_format->cbSize = 0;
DCHECK(IsSupportedFormatForConversion(*dmo_output_format));
// Store the format used.
input_format_.wFormatTag = dmo_output_format->wFormatTag;
input_format_.nChannels = dmo_output_format->nChannels;
input_format_.nSamplesPerSec = dmo_output_format->nSamplesPerSec;
input_format_.wBitsPerSample = dmo_output_format->wBitsPerSample;
input_format_.nBlockAlign = dmo_output_format->nBlockAlign;
input_format_.nAvgBytesPerSec = dmo_output_format->nAvgBytesPerSec;
input_format_.cbSize = dmo_output_format->cbSize;
hr = voice_capture_dmo_->SetOutputType(0, &mt, 0);
MoFreeMediaType(&mt);
if (FAILED(hr)) {
DLOG(ERROR) << "Setting DMO output type failed.";
return false;
}
// We use 10 ms buffer size for the DMO.
endpoint_buffer_size_frames_ = input_format_.nSamplesPerSec / 100;
return true;
}
bool WASAPIAudioInputStream::CreateDummyRenderClientsForDmo() {
Microsoft::WRL::ComPtr<IAudioClient> audio_client(CoreAudioUtil::CreateClient(
AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole));
if (!audio_client.Get()) {
DLOG(ERROR) << "Failed to create audio client for rendering.";
return false;
}
WAVEFORMATPCMEX mix_format;
HRESULT hr =
CoreAudioUtil::GetSharedModeMixFormat(audio_client.Get(), &mix_format);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get mix format.";
return false;
}
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
0, // Stream flags
0, // Buffer duration
0, // Device period
reinterpret_cast<WAVEFORMATEX*>(&mix_format),
NULL);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to initalize audio client for rendering.";
return false;
}
Microsoft::WRL::ComPtr<IAudioRenderClient> audio_render_client =
CoreAudioUtil::CreateRenderClient(audio_client.Get());
if (!audio_render_client.Get()) {
DLOG(ERROR) << "Failed to create audio render client.";
return false;
}
audio_client_for_render_ = audio_client;
audio_render_client_ = audio_render_client;
return true;
}
double WASAPIAudioInputStream::ProvideInput(AudioBus* audio_bus,
uint32_t frames_delayed) {
fifo_->Consume()->CopyTo(audio_bus);
......
......@@ -58,6 +58,7 @@
#include <Audioclient.h>
#include <MMDeviceAPI.h>
#include <dmo.h>
#include <endpointvolume.h>
#include <stddef.h>
#include <stdint.h>
......@@ -75,7 +76,7 @@
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "media/audio/agc_audio_stream.h"
#include "media/audio/audio_manager.h"
#include "media/audio/win/audio_manager_win.h"
#include "media/base/audio_converter.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_export.h"
......@@ -84,7 +85,6 @@ namespace media {
class AudioBlockFifo;
class AudioBus;
class AudioManagerWin;
// AudioInputStream implementation using Windows Core Audio APIs.
class MEDIA_EXPORT WASAPIAudioInputStream
......@@ -94,10 +94,12 @@ class MEDIA_EXPORT WASAPIAudioInputStream
public:
// The ctor takes all the usual parameters, plus |manager| which is the
// the audio manager who is creating this object.
WASAPIAudioInputStream(AudioManagerWin* manager,
WASAPIAudioInputStream(
AudioManagerWin* manager,
const AudioParameters& params,
const std::string& device_id,
const AudioManager::LogCallback& log_callback);
const AudioManager::LogCallback& log_callback,
AudioManagerBase::VoiceProcessingMode voice_processing_mode);
// The dtor is typically called by the AudioManager only and it is usually
// triggered by calling AudioInputStream::Close().
......@@ -117,16 +119,27 @@ class MEDIA_EXPORT WASAPIAudioInputStream
bool started() const { return started_; }
private:
// DelegateSimpleThread::Delegate implementation.
// DelegateSimpleThread::Delegate implementation. Calls either
// RunWithAudioCaptureClient() or RunWithDmo().
void Run() override;
// Pulls capture data from the endpoint device and pushes it to the sink.
// Waits for an event that the audio capture client has data ready.
bool RunWithAudioCaptureClient();
// Polls the DMO (voice processing component) for data every 5 ms.
bool RunWithDmo();
// Pulls capture data from the audio capture client and pushes it to the sink.
void PullCaptureDataAndPushToSink();
// Pulls capture data from the DMO and pushes it to the sink.
void PullDmoCaptureDataAndPushToSink();
// Issues the OnError() callback to the |sink_|.
void HandleError(HRESULT err);
// The Open() method is divided into these sub methods.
// The Open() method is divided into these sub methods when not using the
// voice processing DMO.
HRESULT SetCaptureDevice();
HRESULT GetAudioEngineStreamFormat();
// Returns whether the desired format is supported or not and writes the
......@@ -134,14 +147,22 @@ class MEDIA_EXPORT WASAPIAudioInputStream
// function returns false with |*hr| == S_FALSE, the OS supports a closest
// match but we don't support conversion to it.
bool DesiredFormatIsSupported(HRESULT* hr);
void SetupConverterAndStoreFormatInfo();
HRESULT InitializeAudioEngine();
void ReportOpenResult(HRESULT hr) const;
// Reports stats for format related audio client initilization
// (IAudioClient::Initialize) errors, that is if |hr| is an error related to
// the format.
void MaybeReportFormatRelatedInitError(HRESULT hr) const;
// The Open() method is divided into these sub methods when using the voice
// processing DMO. In addition, SetupConverterAndStoreFormatInfo() above is
// also called.
bool InitializeDmo();
bool SetDmoProperties();
bool SetDmoFormat();
bool CreateDummyRenderClientsForDmo();
// AudioConverter::InputCallback implementation.
double ProvideInput(AudioBus* audio_bus, uint32_t frames_delayed) override;
......@@ -189,30 +210,35 @@ class MEDIA_EXPORT WASAPIAudioInputStream
std::unique_ptr<base::DelegateSimpleThread> capture_thread_;
// Contains the desired output audio format which is set up at construction,
// that is the audio format this class should output data to the sink in.
// that is the audio format this class should output data to the sink in, that
// is the format after the converter.
WAVEFORMATEX output_format_;
// Contains the audio format we get data from the audio engine in. Set to
// |output_format_| at construction and might be changed to a close match
// if the audio engine doesn't support the originally set format.
// if the audio engine doesn't support the originally set format, or to the
// format the voice capture DMO outputs if it's used. Note that this is also
// the format after the fifo, i.e. the input format to the converter if any.
WAVEFORMATEX input_format_;
bool opened_ = false;
bool started_ = false;
StreamOpenResult open_result_ = OPEN_RESULT_OK;
// Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM)
size_t frame_size_ = 0;
// Size in bytes of each audio frame before the converter (4 bytes for 16-bit
// stereo PCM). Note that this is the same before and after the fifo.
size_t frame_size_bytes_ = 0;
// Size in audio frames of each audio packet where an audio packet
// is defined as the block of data which the user received in each
// OnData() callback.
// Size in audio frames of each audio packet (buffer) after the fifo but
// before the converter.
size_t packet_size_frames_ = 0;
// Size in bytes of each audio packet.
// Size in bytes of each audio packet (buffer) after the fifo but before the
// converter.
size_t packet_size_bytes_ = 0;
// Length of the audio endpoint buffer.
// Length of the audio endpoint buffer, or the buffer size used for the DMO.
// That is, the buffer size before the fifo.
uint32_t endpoint_buffer_size_frames_ = 0;
// Contains the unique name of the selected endpoint device.
......@@ -295,6 +321,22 @@ class MEDIA_EXPORT WASAPIAudioInputStream
UINT64 total_lost_frames_ = 0;
UINT64 largest_glitch_frames_ = 0;
// Indicates if the voice processing DMO should be used.
bool use_voice_processing_ = false;
// The voice processing DMO and its data buffer.
Microsoft::WRL::ComPtr<IMediaObject> voice_capture_dmo_;
Microsoft::WRL::ComPtr<IMediaBuffer> media_buffer_;
// Dummy rendering when using the DMO. The DMO requires audio rendering to the
// device it's set up to use, otherwise it won't produce any capture audio
// data. Normally, when the DMO is used there's a render stream, but it's not
// guaranteed so we need to support the lack of it. We do this by always
// opening a render client and rendering silence to it when the DMO is
// running.
Microsoft::WRL::ComPtr<IAudioClient> audio_client_for_render_;
Microsoft::WRL::ComPtr<IAudioRenderClient> audio_render_client_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(WASAPIAudioInputStream);
......
......@@ -170,11 +170,16 @@ static bool HasCoreAudioAndInputDevices(AudioManager* audio_man) {
// also allows the user to modify the default settings.
class AudioInputStreamWrapper {
public:
explicit AudioInputStreamWrapper(AudioManager* audio_manager)
explicit AudioInputStreamWrapper(AudioManager* audio_manager,
bool use_voice_processing)
: audio_man_(audio_manager) {
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
AudioDeviceDescription::kDefaultDeviceId, false, &default_params_)));
EXPECT_EQ(format(), AudioParameters::AUDIO_PCM_LOW_LATENCY);
if (use_voice_processing) {
default_params_.set_effects(default_params_.effects() |
AudioParameters::ECHO_CANCELLER);
}
frames_per_buffer_ = default_params_.frames_per_buffer();
}
......@@ -222,8 +227,9 @@ class AudioInputStreamWrapper {
// Convenience method which creates a default AudioInputStream object.
static AudioInputStream* CreateDefaultAudioInputStream(
AudioManager* audio_manager) {
AudioInputStreamWrapper aisw(audio_manager);
AudioManager* audio_manager,
bool use_voice_processing) {
AudioInputStreamWrapper aisw(audio_manager, use_voice_processing);
AudioInputStream* ais = aisw.Create();
return ais;
}
......@@ -258,7 +264,9 @@ class ScopedAudioInputStream {
DISALLOW_COPY_AND_ASSIGN(ScopedAudioInputStream);
};
class WinAudioInputTest : public ::testing::Test {
// The test class. The boolean parameter specifies if voice processing should be
// used.
class WinAudioInputTest : public ::testing::TestWithParam<bool> {
public:
WinAudioInputTest() {
audio_manager_ =
......@@ -293,27 +301,27 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) {
}
// Test Create(), Close() calling sequence.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamCreateAndClose) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamCreateAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
ScopedAudioInputStream ais(
CreateDefaultAudioInputStream(audio_manager_.get()));
CreateDefaultAudioInputStream(audio_manager_.get(), GetParam()));
ais.Close();
}
// Test Open(), Close() calling sequence.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenAndClose) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamOpenAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
ScopedAudioInputStream ais(
CreateDefaultAudioInputStream(audio_manager_.get()));
CreateDefaultAudioInputStream(audio_manager_.get(), GetParam()));
EXPECT_TRUE(ais->Open());
ais.Close();
}
// Test Open(), Start(), Close() calling sequence.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
ScopedAudioInputStream ais(
CreateDefaultAudioInputStream(audio_manager_.get()));
CreateDefaultAudioInputStream(audio_manager_.get(), GetParam()));
EXPECT_TRUE(ais->Open());
MockAudioInputCallback sink;
ais->Start(&sink);
......@@ -321,10 +329,10 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) {
}
// Test Open(), Start(), Stop(), Close() calling sequence.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
ScopedAudioInputStream ais(
CreateDefaultAudioInputStream(audio_manager_.get()));
CreateDefaultAudioInputStream(audio_manager_.get(), GetParam()));
EXPECT_TRUE(ais->Open());
MockAudioInputCallback sink;
ais->Start(&sink);
......@@ -333,10 +341,10 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) {
}
// Test some additional calling sequences.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
ScopedAudioInputStream ais(
CreateDefaultAudioInputStream(audio_manager_.get()));
CreateDefaultAudioInputStream(audio_manager_.get(), GetParam()));
// Open(), Open() should fail the second time.
EXPECT_TRUE(ais->Open());
......@@ -358,7 +366,7 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) {
ais.Close();
}
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
int count = 0;
......@@ -367,7 +375,7 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) {
// Create default WASAPI input stream which records in stereo using
// the shared mixing rate. The default buffer size is 10ms.
AudioInputStreamWrapper aisw(audio_manager_.get());
AudioInputStreamWrapper aisw(audio_manager_.get(), GetParam());
ScopedAudioInputStream ais(aisw.Create());
EXPECT_TRUE(ais->Open());
......@@ -441,7 +449,7 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) {
}
// Test that we can capture a stream in loopback.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamLoopback) {
TEST_P(WinAudioInputTest, WASAPIAudioInputStreamLoopback) {
AudioDeviceInfoAccessorForTests device_info_accessor(audio_manager_.get());
ABORT_AUDIO_TEST_IF_NOT(device_info_accessor.HasAudioOutputDevices() &&
CoreAudioUtil::IsSupported());
......@@ -475,7 +483,7 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamLoopback) {
// To include disabled tests in test execution, just invoke the test program
// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
// environment variable to a value greater than 0.
TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
TEST_P(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
// Name of the output PCM file containing captured data. The output file
......@@ -483,7 +491,7 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
// Example of full name: \src\build\Debug\out_stereo_10sec.pcm.
const char* file_name = "out_10sec.pcm";
AudioInputStreamWrapper aisw(audio_manager_.get());
AudioInputStreamWrapper aisw(audio_manager_.get(), GetParam());
ScopedAudioInputStream ais(aisw.Create());
ASSERT_TRUE(ais->Open());
......@@ -497,7 +505,7 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
ais.Close();
}
TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) {
TEST_P(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
// This is basically the same test as WASAPIAudioInputStreamRecordToFile
......@@ -532,6 +540,8 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) {
// Otherwise (e.g. 44.1kHz, 22.05kHz etc) we convert to 48kHz.
const int hw_sample_rate = params.sample_rate();
params.Reset(params.format(), test.layout, test.rate, test.frames);
if (GetParam())
params.set_effects(params.effects() | AudioParameters::ECHO_CANCELLER);
std::string file_name(base::StringPrintf(
"resampled_10sec_%i_to_%i_%s.pcm", hw_sample_rate, params.sample_rate(),
......@@ -555,4 +565,8 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) {
}
}
INSTANTIATE_TEST_CASE_P(/* Intentially left empty */,
WinAudioInputTest,
::testing::Bool());
} // namespace media
......@@ -23,6 +23,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/win/windows_version.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_features.h"
#include "media/audio/audio_io.h"
#include "media/audio/win/audio_device_listener_win.h"
#include "media/audio/win/audio_low_latency_input_win.h"
......@@ -178,6 +179,9 @@ AudioParameters AudioManagerWin::GetInputStreamParameters(
if (user_buffer_size)
parameters.set_frames_per_buffer(user_buffer_size);
parameters.set_effects(parameters.effects() |
AudioParameters::EXPERIMENTAL_ECHO_CANCELLER);
return parameters;
}
......@@ -248,7 +252,14 @@ AudioInputStream* AudioManagerWin::MakeLowLatencyInputStream(
const LogCallback& log_callback) {
// Used for both AUDIO_PCM_LOW_LATENCY and AUDIO_PCM_LINEAR.
DVLOG(1) << "MakeLowLatencyInputStream: " << device_id;
return new WASAPIAudioInputStream(this, params, device_id, log_callback);
VoiceProcessingMode voice_processing_mode =
params.effects() & AudioParameters::ECHO_CANCELLER
? VoiceProcessingMode::kEnabled
: VoiceProcessingMode::kDisabled;
return new WASAPIAudioInputStream(this, params, device_id, log_callback,
voice_processing_mode);
}
std::string AudioManagerWin::GetDefaultInputDeviceID() {
......
......@@ -976,4 +976,71 @@ bool CoreAudioUtil::GetDxDiagDetails(std::string* driver_name,
return true;
}
HRESULT CoreAudioUtil::GetDeviceCollectionIndex(const std::string& device_id,
EDataFlow data_flow,
WORD* index) {
ComPtr<IMMDeviceEnumerator> enumerator = CreateDeviceEnumerator();
if (!enumerator.Get()) {
DLOG(ERROR) << "Failed to create device enumerator.";
return E_FAIL;
}
ComPtr<IMMDeviceCollection> device_collection;
HRESULT hr = enumerator->EnumAudioEndpoints(data_flow, DEVICE_STATE_ACTIVE,
&device_collection);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get device collection.";
return hr;
}
UINT number_of_devices = 0;
hr = device_collection->GetCount(&number_of_devices);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get device collection count.";
return hr;
}
ComPtr<IMMDevice> device;
for (WORD i = 0; i < number_of_devices; ++i) {
hr = device_collection->Item(i, &device);
if (FAILED(hr)) {
DLOG(WARNING) << "Failed to get device.";
continue;
}
ScopedCoMem<WCHAR> current_device_id;
hr = device->GetId(&current_device_id);
if (FAILED(hr)) {
DLOG(WARNING) << "Failed to get device id.";
continue;
}
if (base::UTF16ToUTF8(current_device_id.get()) == device_id) {
*index = i;
return S_OK;
}
}
DVLOG(1) << "No matching device found.";
return S_FALSE;
}
HRESULT CoreAudioUtil::SetBoolProperty(IPropertyStore* property_store,
REFPROPERTYKEY key,
VARIANT_BOOL value) {
base::win::ScopedPropVariant pv;
PROPVARIANT* pv_ptr = pv.Receive();
pv_ptr->vt = VT_BOOL;
pv_ptr->boolVal = value;
return property_store->SetValue(key, pv.get());
}
HRESULT CoreAudioUtil::SetVtI4Property(IPropertyStore* property_store,
REFPROPERTYKEY key,
LONG value) {
base::win::ScopedPropVariant pv;
PROPVARIANT* pv_ptr = pv.Receive();
pv_ptr->vt = VT_I4;
pv_ptr->lVal = value;
return property_store->SetValue(key, pv.get());
}
} // namespace media
......@@ -212,6 +212,23 @@ class MEDIA_EXPORT CoreAudioUtil {
static bool GetDxDiagDetails(std::string* driver_name,
std::string* driver_version);
// Gets the device collection index for the device specified by |device_id|.
// If the device is found in the device collection, the index is written to
// |*index| and S_OK is returned. If the device is not found, S_FALSE is
// returned and |*index| is left unchanged. In case of an error, the error
// result is returned and |*index| is left unchanged.
static HRESULT GetDeviceCollectionIndex(const std::string& device_id,
EDataFlow data_flow,
WORD* index);
// Sets the property identified by |key| to |value| in |*property_store|.
static HRESULT SetBoolProperty(IPropertyStore* property_store,
REFPROPERTYKEY key,
VARIANT_BOOL value);
static HRESULT SetVtI4Property(IPropertyStore* property_store,
REFPROPERTYKEY key,
LONG value);
private:
CoreAudioUtil() {}
~CoreAudioUtil() {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment