Commit e61748fb authored by henrika's avatar henrika Committed by Henrik Andreasson

Add support for raw audio capture on Windows behind a flag

As discussed: landing behind a flag so we can learn more by testing it
on more Windows laptops. Will eventually be launched using an experiment.
This round is mainly for local testing purposes and to add UMA so we
can track how many devices that support raw audio capture.

NOTRY=true

Tbr: beccahughes
Bug: 1133643
Change-Id: Iae1a5ad45fb8ca7db192e0d3ea779e3803d372d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2440055Reviewed-by: default avatarGuido Urdaneta <guidou@chromium.org>
Reviewed-by: default avatarMarkus Handell <handellm@google.com>
Reviewed-by: default avatarOlga Sharonova <olka@chromium.org>
Reviewed-by: default avatarMark Pearson <mpearson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812656}
parent 5a2a2a10
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
#include "media/audio/win/audio_low_latency_input_win.h" #include "media/audio/win/audio_low_latency_input_win.h"
#include <objbase.h> #include <objbase.h>
#include <propkey.h>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <memory> #include <memory>
#include <utility>
#include "base/logging.h" #include "base/logging.h"
#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_functions.h"
...@@ -16,6 +18,8 @@ ...@@ -16,6 +18,8 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "base/win/scoped_propvariant.h"
#include "base/win/scoped_variant.h"
#include "media/audio/audio_device_description.h" #include "media/audio/audio_device_description.h"
#include "media/audio/audio_features.h" #include "media/audio/audio_features.h"
#include "media/audio/win/avrt_wrapper_win.h" #include "media/audio/win/avrt_wrapper_win.h"
...@@ -25,6 +29,7 @@ ...@@ -25,6 +29,7 @@
#include "media/base/audio_timestamp_helper.h" #include "media/base/audio_timestamp_helper.h"
#include "media/base/channel_layout.h" #include "media/base/channel_layout.h"
#include "media/base/limits.h" #include "media/base/limits.h"
#include "media/base/media_switches.h"
using base::win::ScopedCOMInitializer; using base::win::ScopedCOMInitializer;
...@@ -130,6 +135,17 @@ const char* StreamOpenResultToString( ...@@ -130,6 +135,17 @@ const char* StreamOpenResultToString(
return "UNKNOWN"; return "UNKNOWN";
} }
bool VariantBoolToBool(VARIANT_BOOL var_bool) {
switch (var_bool) {
case VARIANT_TRUE:
return true;
case VARIANT_FALSE:
return false;
}
LOG(ERROR) << "Invalid VARIANT_BOOL type";
return false;
}
std::string GetOpenLogString(WASAPIAudioInputStream::StreamOpenResult result, std::string GetOpenLogString(WASAPIAudioInputStream::StreamOpenResult result,
HRESULT hr, HRESULT hr,
WAVEFORMATEXTENSIBLE input_format, WAVEFORMATEXTENSIBLE input_format,
...@@ -250,6 +266,9 @@ bool WASAPIAudioInputStream::Open() { ...@@ -250,6 +266,9 @@ bool WASAPIAudioInputStream::Open() {
return false; return false;
} }
// Check if raw audio processing is supported for the selected capture device.
raw_processing_supported_ = RawProcessingSupported();
// Obtain an IAudioClient interface which enables us to create and initialize // Obtain an IAudioClient interface which enables us to create and initialize
// an audio stream between an audio application and the audio engine. // an audio stream between an audio application and the audio engine.
hr = endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, hr = endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
...@@ -267,8 +286,17 @@ bool WASAPIAudioInputStream::Open() { ...@@ -267,8 +286,17 @@ bool WASAPIAudioInputStream::Open() {
hr = GetAudioEngineStreamFormat(); hr = GetAudioEngineStreamFormat();
#endif #endif
// Attempt to enable communications category and raw capture mode on the audio
// stream. Ignoring return value since the method logs its own error messages
// and it should be OK to continue opening the stream even after a failure.
if (base::FeatureList::IsEnabled(media::kWasapiRawAudioCapture) &&
raw_processing_supported_ &&
!AudioDeviceDescription::IsLoopbackDevice(device_id_)) {
SetCommunicationsCategoryAndRawCaptureMode();
}
// Verify that the selected audio endpoint supports the specified format // Verify that the selected audio endpoint supports the specified format
// set during construction. // set during construction and using the specified client properties.
hr = S_OK; hr = S_OK;
if (!DesiredFormatIsSupported(&hr)) { if (!DesiredFormatIsSupported(&hr)) {
open_result_ = OPEN_RESULT_FORMAT_NOT_SUPPORTED; open_result_ = OPEN_RESULT_FORMAT_NOT_SUPPORTED;
...@@ -415,6 +443,15 @@ void WASAPIAudioInputStream::Close() { ...@@ -415,6 +443,15 @@ void WASAPIAudioInputStream::Close() {
// It is also valid to call Close() after Start() has been called. // It is also valid to call Close() after Start() has been called.
Stop(); Stop();
// Only upload UMA histogram for the case when AGC is enabled, i.e., for
// WebRTC based audio input streams.
if (GetAutomaticGainControl()) {
// Upload UMA histogram to track if the capture device supported raw audio
// capture or not. See https://crbug.com/1133643.
base::UmaHistogramBoolean("Media.Audio.RawProcessingSupportedWin",
raw_processing_supported_);
}
if (converter_) if (converter_)
converter_->RemoveInput(this); converter_->RemoveInput(this);
...@@ -809,6 +846,68 @@ HRESULT WASAPIAudioInputStream::GetAudioEngineStreamFormat() { ...@@ -809,6 +846,68 @@ HRESULT WASAPIAudioInputStream::GetAudioEngineStreamFormat() {
return hr; return hr;
} }
bool WASAPIAudioInputStream::RawProcessingSupported() {
DCHECK(endpoint_device_.Get());
// Check if System.Devices.AudioDevice.RawProcessingSupported can be found
// and queried in the Windows Property System. It corresponds to raw
// processing mode support for the specified audio device. If its value is
// VARIANT_TRUE the device supports raw processing mode.
bool raw_processing_supported = false;
Microsoft::WRL::ComPtr<IPropertyStore> properties;
base::win::ScopedPropVariant raw_processing;
if (FAILED(endpoint_device_->OpenPropertyStore(STGM_READ, &properties)) ||
FAILED(
properties->GetValue(PKEY_Devices_AudioDevice_RawProcessingSupported,
raw_processing.Receive())) ||
raw_processing.get().vt != VT_BOOL) {
SendLogMessage(
"%s => (WARNING: failed to access "
"System.Devices.AudioDevice.RawProcessingSupported)",
__func__);
} else {
raw_processing_supported = VariantBoolToBool(raw_processing.get().boolVal);
SendLogMessage(
"%s => (System.Devices.AudioDevice.RawProcessingSupported=%s)",
__func__, raw_processing_supported ? "true" : "false");
}
return raw_processing_supported;
}
HRESULT WASAPIAudioInputStream::SetCommunicationsCategoryAndRawCaptureMode() {
DCHECK(audio_client_.Get());
DCHECK(!AudioDeviceDescription::IsLoopbackDevice(device_id_));
DCHECK(raw_processing_supported_);
SendLogMessage("%s()", __func__);
Microsoft::WRL::ComPtr<IAudioClient2> audio_client2;
HRESULT hr = audio_client_.As(&audio_client2);
if (FAILED(hr)) {
SendLogMessage("%s => (ERROR: IAudioClient2 is not supported)", __func__);
return hr;
}
// Use IAudioClient2::SetClientProperties() to set communications category
// and to enable raw stream capture if it is supported.
if (audio_client2.Get()) {
AudioClientProperties audio_props = {0};
audio_props.cbSize = sizeof(AudioClientProperties);
audio_props.bIsOffload = false;
// AudioCategory_Communications opts us in to communications policy and
// communications processing. AUDCLNT_STREAMOPTIONS_RAW turns off the
// processing, but not the policy.
audio_props.eCategory = AudioCategory_Communications;
// The audio stream is a 'raw' stream that bypasses all signal processing
// except for endpoint specific, always-on processing in the Audio
// Processing Object (APO), driver, and hardware.
audio_props.Options = AUDCLNT_STREAMOPTIONS_RAW;
hr = audio_client2->SetClientProperties(&audio_props);
if (FAILED(hr)) {
SendLogMessage("%s => (ERROR: IAudioClient2::SetClientProperties=[%s])",
__func__, ErrorToString(hr).c_str());
}
}
return hr;
}
bool WASAPIAudioInputStream::DesiredFormatIsSupported(HRESULT* hr) { bool WASAPIAudioInputStream::DesiredFormatIsSupported(HRESULT* hr) {
SendLogMessage("%s()", __func__); SendLogMessage("%s()", __func__);
// An application that uses WASAPI to manage shared-mode streams can rely // An application that uses WASAPI to manage shared-mode streams can rely
...@@ -1014,7 +1113,7 @@ HRESULT WASAPIAudioInputStream::InitializeAudioEngine() { ...@@ -1014,7 +1113,7 @@ HRESULT WASAPIAudioInputStream::InitializeAudioEngine() {
// //
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd316551(v=vs.85).aspx // http://msdn.microsoft.com/en-us/library/windows/desktop/dd316551(v=vs.85).aspx
if (AudioDeviceDescription::IsLoopbackDevice(device_id_)) { if (AudioDeviceDescription::IsLoopbackDevice(device_id_)) {
SendLogMessage("%s => (WARNING: loopback mode is selected", __func__); SendLogMessage("%s => (WARNING: loopback mode is selected)", __func__);
hr = endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, hr = endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
&audio_render_client_for_loopback_); &audio_render_client_for_loopback_);
if (FAILED(hr)) { if (FAILED(hr)) {
......
...@@ -154,6 +154,10 @@ class MEDIA_EXPORT WASAPIAudioInputStream ...@@ -154,6 +154,10 @@ class MEDIA_EXPORT WASAPIAudioInputStream
// The Open() method is divided into these sub methods. // The Open() method is divided into these sub methods.
HRESULT SetCaptureDevice(); HRESULT SetCaptureDevice();
// Returns whether raw audio processing is supported or not for the selected
// capture device.
bool RawProcessingSupported();
HRESULT SetCommunicationsCategoryAndRawCaptureMode();
HRESULT GetAudioEngineStreamFormat(); HRESULT GetAudioEngineStreamFormat();
// Returns whether the desired format is supported or not and writes the // Returns whether the desired format is supported or not and writes the
// result of a failing system call to |*hr|, or S_OK if successful. If this // result of a failing system call to |*hr|, or S_OK if successful. If this
...@@ -298,6 +302,10 @@ class MEDIA_EXPORT WASAPIAudioInputStream ...@@ -298,6 +302,10 @@ class MEDIA_EXPORT WASAPIAudioInputStream
// session starts. Utilized in UMA histogram. // session starts. Utilized in UMA histogram.
bool audio_session_starts_at_zero_volume_ = false; bool audio_session_starts_at_zero_volume_ = false;
// Set to true if the selected audio device supports raw audio capture.
// Also added to a UMS histogram.
bool raw_processing_supported_ = false;
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(WASAPIAudioInputStream); DISALLOW_COPY_AND_ASSIGN(WASAPIAudioInputStream);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
#include "base/test/test_timeouts.h" #include "base/test/test_timeouts.h"
#include "base/win/scoped_com_initializer.h" #include "base/win/scoped_com_initializer.h"
...@@ -29,6 +30,7 @@ ...@@ -29,6 +30,7 @@
#include "media/audio/audio_unittest_util.h" #include "media/audio/audio_unittest_util.h"
#include "media/audio/test_audio_thread.h" #include "media/audio/test_audio_thread.h"
#include "media/audio/win/core_audio_util_win.h" #include "media/audio/win/core_audio_util_win.h"
#include "media/base/media_switches.h"
#include "media/base/seekable_buffer.h" #include "media/base/seekable_buffer.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -258,7 +260,8 @@ class ScopedAudioInputStream { ...@@ -258,7 +260,8 @@ class ScopedAudioInputStream {
DISALLOW_COPY_AND_ASSIGN(ScopedAudioInputStream); DISALLOW_COPY_AND_ASSIGN(ScopedAudioInputStream);
}; };
class WinAudioInputTest : public ::testing::Test { class WinAudioInputTest : public ::testing::Test,
public ::testing::WithParamInterface<bool> {
public: public:
WinAudioInputTest() { WinAudioInputTest() {
audio_manager_ = audio_manager_ =
...@@ -350,8 +353,13 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) { ...@@ -350,8 +353,13 @@ TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) {
} }
// Test Open(), Start(), Stop(), Close() calling sequence. // Test Open(), Start(), Stop(), Close() calling sequence.
TEST_F(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) { TEST_P(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get())); ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
base::test::ScopedFeatureList feature_list;
const bool use_raw_audio = GetParam();
use_raw_audio
? feature_list.InitAndEnableFeature(media::kWasapiRawAudioCapture)
: feature_list.InitAndDisableFeature(media::kWasapiRawAudioCapture);
ScopedAudioInputStream ais( ScopedAudioInputStream ais(
CreateDefaultAudioInputStream(audio_manager_.get())); CreateDefaultAudioInputStream(audio_manager_.get()));
EXPECT_TRUE(ais->Open()); EXPECT_TRUE(ais->Open());
...@@ -526,6 +534,33 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { ...@@ -526,6 +534,33 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
ais.Close(); ais.Close();
} }
// As above, intended for manual testing only but this time using the raw
// capture mode.
TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFileRAW) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(media::kWasapiRawAudioCapture);
// Name of the output PCM file containing captured data. The output file
// will be stored in the directory containing 'media_unittests.exe'.
// Example of full name: \src\build\Debug\out_stereo_10sec_raw.pcm.
const char* file_name = "out_10sec_raw.pcm";
AudioInputStreamWrapper aisw(audio_manager_.get());
ScopedAudioInputStream ais(aisw.Create());
ASSERT_TRUE(ais->Open());
VLOG(0) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]";
WriteToFileAudioSink file_sink(file_name);
VLOG(0) << ">> Speak into the default microphone while recording.";
ais->Start(&file_sink);
base::PlatformThread::Sleep(TestTimeouts::action_timeout());
ais->Stop();
VLOG(0) << ">> Recording has stopped.";
ais.Close();
}
TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) { TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get())); ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get()));
...@@ -577,11 +612,14 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) { ...@@ -577,11 +612,14 @@ TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) {
VLOG(0) << ">> Speak into the default microphone while recording."; VLOG(0) << ">> Speak into the default microphone while recording.";
ais->Start(&file_sink); ais->Start(&file_sink);
base::PlatformThread::Sleep(TestTimeouts::action_timeout()); base::PlatformThread::Sleep(TestTimeouts::action_timeout());
// base::PlatformThread::Sleep(base::TimeDelta::FromMinutes(10));
ais->Stop(); ais->Stop();
VLOG(0) << ">> Recording has stopped."; VLOG(0) << ">> Recording has stopped.";
ais.Close(); ais.Close();
} }
} }
INSTANTIATE_TEST_SUITE_P(WinAudioInputTests,
WinAudioInputTest,
testing::Bool());
} // namespace media } // namespace media
...@@ -655,6 +655,15 @@ const base::Feature kMediaFoundationVideoCapture{ ...@@ -655,6 +655,15 @@ const base::Feature kMediaFoundationVideoCapture{
const base::Feature MEDIA_EXPORT kMediaFoundationVP8Decoding{ const base::Feature MEDIA_EXPORT kMediaFoundationVP8Decoding{
"MediaFoundationVP8Decoding", base::FEATURE_DISABLED_BY_DEFAULT}; "MediaFoundationVP8Decoding", base::FEATURE_DISABLED_BY_DEFAULT};
// Use the AUDCLNT_STREAMOPTIONS_RAW option on WASAPI input audio streams in
// combination with the IAudioClient2::SetClientProperties() API.
// The audio stream is a 'raw' stream that bypasses all signal processing except
// for endpoint specific, always-on processing in the Audio Processing Object
// (APO), driver, and hardware.
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-audclnt_streamoptions
const base::Feature MEDIA_EXPORT kWasapiRawAudioCapture{
"WASAPIRawAudioCapture", base::FEATURE_DISABLED_BY_DEFAULT};
#endif // defined(OS_WIN) #endif // defined(OS_WIN)
#if defined(OS_MAC) #if defined(OS_MAC)
......
...@@ -221,6 +221,7 @@ MEDIA_EXPORT extern const base::Feature kMediaFoundationAsyncH264Encoding; ...@@ -221,6 +221,7 @@ MEDIA_EXPORT extern const base::Feature kMediaFoundationAsyncH264Encoding;
MEDIA_EXPORT extern const base::Feature kMediaFoundationAV1Decoding; MEDIA_EXPORT extern const base::Feature kMediaFoundationAV1Decoding;
MEDIA_EXPORT extern const base::Feature kMediaFoundationVideoCapture; MEDIA_EXPORT extern const base::Feature kMediaFoundationVideoCapture;
MEDIA_EXPORT extern const base::Feature kMediaFoundationVP8Decoding; MEDIA_EXPORT extern const base::Feature kMediaFoundationVP8Decoding;
MEDIA_EXPORT extern const base::Feature kWasapiRawAudioCapture;
#endif // defined(OS_WIN) #endif // defined(OS_WIN)
#if defined(OS_MAC) #if defined(OS_MAC)
......
...@@ -497,6 +497,18 @@ reviews. Googlers can read more about this at go/gwsq-gerrit. ...@@ -497,6 +497,18 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</summary> </summary>
</histogram> </histogram>
<histogram name="Media.Audio.RawProcessingSupportedWin" enum="BooleanSupported"
expires_after="2021-09-30">
<owner>henrika@chromium.org</owner>
<owner>media-dev@chromium.org</owner>
<summary>
Whether the capture device supports raw audio capture or not. Emitted when
the audio input stream is closed but only on Windows platforms. Only
uploaded for the case when analog AGC is enabled, i.e., for WebRTC-based
audio input streams.
</summary>
</histogram>
<histogram name="Media.Audio.Render.FramesRequested" units="frames" <histogram name="Media.Audio.Render.FramesRequested" units="frames"
expires_after="2021-04-05"> expires_after="2021-04-05">
<owner>guidou@chromium.org</owner> <owner>guidou@chromium.org</owner>
......
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