diff --git a/modules/audio_device/BUILD.gn b/modules/audio_device/BUILD.gn index 9050faf647..4b58779d58 100644 --- a/modules/audio_device/BUILD.gn +++ b/modules/audio_device/BUILD.gn @@ -160,6 +160,57 @@ rtc_source_set("audio_device_name") { ] } +rtc_source_set("windows_core_audio_utility") { + if (is_win && !build_with_chromium) { + sources = [ + "win/core_audio_utility_win.cc", + "win/core_audio_utility_win.h", + ] + + deps = [ + ":audio_device_api", + ":audio_device_name", + "../../api/units:time_delta", + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + ] + } +} + +# An ADM with a dedicated factory method which does not depend on the +# audio_device_impl target. The goal is to use this new structure and +# gradually phase out the old design. +# TODO(henrika): currently only supported on Windows. +rtc_source_set("audio_device_module_from_input_and_output") { + visibility = [ ":*" ] + + if (is_win && !build_with_chromium) { + sources = [ + "include/audio_device_factory.cc", + "include/audio_device_factory.h", + ] + sources += [ + "win/audio_device_module_win.cc", + "win/audio_device_module_win.h", + "win/core_audio_base_win.cc", + "win/core_audio_base_win.h", + "win/core_audio_input_win.cc", + "win/core_audio_input_win.h", + "win/core_audio_output_win.cc", + "win/core_audio_output_win.h", + ] + + deps = [ + ":audio_device_api", + ":audio_device_buffer", + ":windows_core_audio_utility", + "../../api:optional", + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + ] + } +} + # Contains default implementations of webrtc::AudioDeviceModule for Windows, # Linux, Mac, iOS and Android. rtc_source_set("audio_device_impl") { @@ -187,9 +238,6 @@ rtc_source_set("audio_device_impl") { if (rtc_include_internal_audio_device && is_ios) { deps += [ ":audio_device_ios_objc" ] } - if (is_win) { - deps += [ ":audio_device_name" ] - } sources = [ "dummy/audio_device_dummy.cc", @@ -339,8 +387,6 @@ rtc_source_set("audio_device_impl") { sources += [ "win/audio_device_core_win.cc", "win/audio_device_core_win.h", - "win/core_audio_utility_win.cc", - "win/core_audio_utility_win.h", ] libs = [ # Required for the built-in WASAPI AEC. @@ -449,8 +495,12 @@ if (rtc_include_tests) { if (is_linux || is_mac || is_win) { sources += [ "audio_device_unittest.cc" ] } - if (is_win && !rtc_use_dummy_audio_file_devices) { + if (is_win) { sources += [ "win/core_audio_utility_win_unittest.cc" ] + deps += [ + ":audio_device_module_from_input_and_output", + ":windows_core_audio_utility", + ] } if (is_android) { # Need to disable error due to the line in diff --git a/modules/audio_device/audio_device_impl.cc b/modules/audio_device/audio_device_impl.cc index 521f766e1d..834f4aa917 100644 --- a/modules/audio_device/audio_device_impl.cc +++ b/modules/audio_device/audio_device_impl.cc @@ -72,6 +72,15 @@ namespace webrtc { rtc::scoped_refptr AudioDeviceModule::Create( const AudioLayer audio_layer) { RTC_LOG(INFO) << __FUNCTION__; + + // The "AudioDeviceModule::kWindowsCoreAudio2" audio layer has its own + // dedicated factory method which should be used instead. + if (audio_layer == AudioDeviceModule::kWindowsCoreAudio2) { + RTC_LOG(LS_ERROR) << "Use the CreateWindowsCoreAudioAudioDeviceModule() " + "factory method instead for this option."; + return nullptr; + } + // Create the generic reference counted (platform independent) implementation. rtc::scoped_refptr audioDevice( new rtc::RefCountedObject(audio_layer)); diff --git a/modules/audio_device/audio_device_unittest.cc b/modules/audio_device/audio_device_unittest.cc index f4893c587b..40b6987089 100644 --- a/modules/audio_device/audio_device_unittest.cc +++ b/modules/audio_device/audio_device_unittest.cc @@ -10,6 +10,7 @@ #include #include +#include #include #include "api/array_view.h" @@ -29,6 +30,10 @@ #include "rtc_base/timeutils.h" #include "test/gmock.h" #include "test/gtest.h" +#ifdef WEBRTC_WIN +#include "modules/audio_device/include/audio_device_factory.h" +#include "modules/audio_device/win/core_audio_utility_win.h" +#endif using ::testing::_; using ::testing::AtLeast; @@ -445,17 +450,17 @@ class MockAudioTransport : public test::MockAudioTransport { }; // AudioDeviceTest test fixture. -class AudioDeviceTest : public ::testing::Test { +class AudioDeviceTest + : public ::testing::TestWithParam { protected: - AudioDeviceTest() : event_(false, false) { + AudioDeviceTest() : audio_layer_(GetParam()), event_(false, false) { #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \ !defined(WEBRTC_DUMMY_AUDIO_BUILD) rtc::LogMessage::LogToDebug(rtc::LS_INFO); // Add extra logging fields here if needed for debugging. - // rtc::LogMessage::LogTimestamps(); - // rtc::LogMessage::LogThreads(); - audio_device_ = - AudioDeviceModule::Create(AudioDeviceModule::kPlatformDefaultAudio); + rtc::LogMessage::LogTimestamps(); + rtc::LogMessage::LogThreads(); + audio_device_ = CreateAudioDevice(); EXPECT_NE(audio_device_.get(), nullptr); AudioDeviceModule::AudioLayer audio_layer; int got_platform_audio_layer = @@ -506,6 +511,32 @@ class AudioDeviceTest : public ::testing::Test { return audio_device_; } + rtc::scoped_refptr CreateAudioDevice() { + // Use the default factory for kPlatformDefaultAudio and a special factory + // CreateWindowsCoreAudioAudioDeviceModule() for kWindowsCoreAudio2. + // The value of |audio_layer_| is set at construction by GetParam() and two + // different layers are tested on Windows only. + if (audio_layer_ == AudioDeviceModule::kPlatformDefaultAudio) { + return AudioDeviceModule::Create(audio_layer_); + } else if (audio_layer_ == AudioDeviceModule::kWindowsCoreAudio2) { +#ifdef WEBRTC_WIN + // We must initialize the COM library on a thread before we calling any of + // the library functions. All COM functions in the ADM will return + // CO_E_NOTINITIALIZED otherwise. + com_initializer_ = rtc::MakeUnique( + webrtc_win::ScopedCOMInitializer::kMTA); + EXPECT_TRUE(com_initializer_->Succeeded()); + EXPECT_TRUE(webrtc_win::core_audio_utility::IsSupported()); + EXPECT_TRUE(webrtc_win::core_audio_utility::IsMMCSSSupported()); + return CreateWindowsCoreAudioAudioDeviceModule(); +#else + return nullptr; +#endif + } else { + return nullptr; + } + } + void StartPlayout() { EXPECT_FALSE(audio_device()->Playing()); EXPECT_EQ(0, audio_device()->InitPlayout()); @@ -534,17 +565,70 @@ class AudioDeviceTest : public ::testing::Test { EXPECT_FALSE(audio_device()->RecordingIsInitialized()); } + bool NewWindowsAudioDeviceModuleIsUsed() { +#ifdef WEBRTC_WIN + AudioDeviceModule::AudioLayer audio_layer; + EXPECT_EQ(0, audio_device()->ActiveAudioLayer(&audio_layer)); + if (audio_layer == AudioDeviceModule::kWindowsCoreAudio2) { + // Default device is always added as first element in the list and the + // default communication device as the second element. Hence, the list + // contains two extra elements in this case. + return true; + } +#endif + return false; + } + private: +#ifdef WEBRTC_WIN + // Windows Core Audio based ADM needs to run on a COM initialized thread. + std::unique_ptr com_initializer_; +#endif + AudioDeviceModule::AudioLayer audio_layer_; bool requirements_satisfied_ = true; rtc::Event event_; rtc::scoped_refptr audio_device_; bool stereo_playout_ = false; }; -// Uses the test fixture to create, initialize and destruct the ADM. -TEST_F(AudioDeviceTest, ConstructDestruct) {} +// Instead of using the test fixture, verify that the different factory methods +// work as intended. +TEST(AudioDeviceTestWin, ConstructDestructWithFactory) { + rtc::scoped_refptr audio_device; + // The default factory should work for all platforms when a default ADM is + // requested. + audio_device = + AudioDeviceModule::Create(AudioDeviceModule::kPlatformDefaultAudio); + EXPECT_TRUE(audio_device); + audio_device = nullptr; +#ifdef WEBRTC_WIN + // For Windows, the old factory method creates an ADM where the platform- + // specific parts are implemented by an AudioDeviceGeneric object. Verify + // that the old factory can't be used in combination with the latest audio + // layer AudioDeviceModule::kWindowsCoreAudio2. + audio_device = + AudioDeviceModule::Create(AudioDeviceModule::kWindowsCoreAudio2); + EXPECT_FALSE(audio_device); + audio_device = nullptr; + // Instead, ensure that the new dedicated factory method called + // CreateWindowsCoreAudioAudioDeviceModule() can be used on Windows and that + // it sets the audio layer to kWindowsCoreAudio2 implicitly. Note that, the + // new ADM for Windows must be created on a COM thread. + webrtc_win::ScopedCOMInitializer com_initializer( + webrtc_win::ScopedCOMInitializer::kMTA); + EXPECT_TRUE(com_initializer.Succeeded()); + audio_device = CreateWindowsCoreAudioAudioDeviceModule(); + EXPECT_TRUE(audio_device); + AudioDeviceModule::AudioLayer audio_layer; + EXPECT_EQ(0, audio_device->ActiveAudioLayer(&audio_layer)); + EXPECT_EQ(audio_layer, AudioDeviceModule::kWindowsCoreAudio2); +#endif +} -TEST_F(AudioDeviceTest, InitTerminate) { +// Uses the test fixture to create, initialize and destruct the ADM. +TEST_P(AudioDeviceTest, ConstructDestructDefault) {} + +TEST_P(AudioDeviceTest, InitTerminate) { SKIP_TEST_IF_NOT(requirements_satisfied()); // Initialization is part of the test fixture. EXPECT_TRUE(audio_device()->Initialized()); @@ -552,28 +636,105 @@ TEST_F(AudioDeviceTest, InitTerminate) { EXPECT_FALSE(audio_device()->Initialized()); } -// Tests Start/Stop playout without any registered audio callback. -TEST_F(AudioDeviceTest, StartStopPlayout) { +// Enumerate all available and active output devices. +TEST_P(AudioDeviceTest, PlayoutDeviceNames) { + SKIP_TEST_IF_NOT(requirements_satisfied()); + char device_name[kAdmMaxDeviceNameSize]; + char unique_id[kAdmMaxGuidSize]; + int num_devices = audio_device()->PlayoutDevices(); + if (NewWindowsAudioDeviceModuleIsUsed()) { + num_devices += 2; + } + EXPECT_GT(num_devices, 0); + for (int i = 0; i < num_devices; ++i) { + EXPECT_EQ(0, audio_device()->PlayoutDeviceName(i, device_name, unique_id)); + } + EXPECT_EQ(-1, audio_device()->PlayoutDeviceName(num_devices, device_name, + unique_id)); +} + +// Enumerate all available and active input devices. +TEST_P(AudioDeviceTest, RecordingDeviceNames) { + SKIP_TEST_IF_NOT(requirements_satisfied()); + char device_name[kAdmMaxDeviceNameSize]; + char unique_id[kAdmMaxGuidSize]; + int num_devices = audio_device()->RecordingDevices(); + if (NewWindowsAudioDeviceModuleIsUsed()) { + num_devices += 2; + } + EXPECT_GT(num_devices, 0); + for (int i = 0; i < num_devices; ++i) { + EXPECT_EQ(0, + audio_device()->RecordingDeviceName(i, device_name, unique_id)); + } + EXPECT_EQ(-1, audio_device()->RecordingDeviceName(num_devices, device_name, + unique_id)); +} + +// Counts number of active output devices and ensure that all can be selected. +TEST_P(AudioDeviceTest, SetPlayoutDevice) { + SKIP_TEST_IF_NOT(requirements_satisfied()); + int num_devices = audio_device()->PlayoutDevices(); + if (NewWindowsAudioDeviceModuleIsUsed()) { + num_devices += 2; + } + EXPECT_GT(num_devices, 0); + // Verify that all available playout devices can be set (not enabled yet). + for (int i = 0; i < num_devices; ++i) { + EXPECT_EQ(0, audio_device()->SetPlayoutDevice(i)); + } + EXPECT_EQ(-1, audio_device()->SetPlayoutDevice(num_devices)); +#ifdef WEBRTC_WIN + // On Windows, verify the alternative method where the user can select device + // by role. + EXPECT_EQ( + 0, audio_device()->SetPlayoutDevice(AudioDeviceModule::kDefaultDevice)); + EXPECT_EQ(0, audio_device()->SetPlayoutDevice( + AudioDeviceModule::kDefaultCommunicationDevice)); +#endif +} + +// Counts number of active input devices and ensure that all can be selected. +TEST_P(AudioDeviceTest, SetRecordingDevice) { + SKIP_TEST_IF_NOT(requirements_satisfied()); + int num_devices = audio_device()->RecordingDevices(); + if (NewWindowsAudioDeviceModuleIsUsed()) { + num_devices += 2; + } + EXPECT_GT(num_devices, 0); + // Verify that all available recording devices can be set (not enabled yet). + for (int i = 0; i < num_devices; ++i) { + EXPECT_EQ(0, audio_device()->SetRecordingDevice(i)); + } + EXPECT_EQ(-1, audio_device()->SetRecordingDevice(num_devices)); +#ifdef WEBRTC_WIN + // On Windows, verify the alternative method where the user can select device + // by role. + EXPECT_EQ( + 0, audio_device()->SetRecordingDevice(AudioDeviceModule::kDefaultDevice)); + EXPECT_EQ(0, audio_device()->SetRecordingDevice( + AudioDeviceModule::kDefaultCommunicationDevice)); +#endif +} + +// Tests Start/Stop playout without any registered audio callback. +TEST_P(AudioDeviceTest, StartStopPlayout) { SKIP_TEST_IF_NOT(requirements_satisfied()); - StartPlayout(); - StopPlayout(); StartPlayout(); StopPlayout(); } // Tests Start/Stop recording without any registered audio callback. -TEST_F(AudioDeviceTest, StartStopRecording) { +TEST_P(AudioDeviceTest, StartStopRecording) { SKIP_TEST_IF_NOT(requirements_satisfied()); StartRecording(); StopRecording(); - StartRecording(); - StopRecording(); } // Tests Init/Stop/Init recording without any registered audio callback. // See https://bugs.chromium.org/p/webrtc/issues/detail?id=8041 for details // on why this test is useful. -TEST_F(AudioDeviceTest, InitStopInitRecording) { +TEST_P(AudioDeviceTest, InitStopInitRecording) { SKIP_TEST_IF_NOT(requirements_satisfied()); EXPECT_EQ(0, audio_device()->InitRecording()); EXPECT_TRUE(audio_device()->RecordingIsInitialized()); @@ -583,7 +744,7 @@ TEST_F(AudioDeviceTest, InitStopInitRecording) { } // Tests Init/Stop/Init recording while playout is active. -TEST_F(AudioDeviceTest, InitStopInitRecordingWhilePlaying) { +TEST_P(AudioDeviceTest, InitStopInitRecordingWhilePlaying) { SKIP_TEST_IF_NOT(requirements_satisfied()); StartPlayout(); EXPECT_EQ(0, audio_device()->InitRecording()); @@ -595,7 +756,7 @@ TEST_F(AudioDeviceTest, InitStopInitRecordingWhilePlaying) { } // Tests Init/Stop/Init playout without any registered audio callback. -TEST_F(AudioDeviceTest, InitStopInitPlayout) { +TEST_P(AudioDeviceTest, InitStopInitPlayout) { SKIP_TEST_IF_NOT(requirements_satisfied()); EXPECT_EQ(0, audio_device()->InitPlayout()); EXPECT_TRUE(audio_device()->PlayoutIsInitialized()); @@ -605,7 +766,7 @@ TEST_F(AudioDeviceTest, InitStopInitPlayout) { } // Tests Init/Stop/Init playout while recording is active. -TEST_F(AudioDeviceTest, InitStopInitPlayoutWhileRecording) { +TEST_P(AudioDeviceTest, InitStopInitPlayoutWhileRecording) { SKIP_TEST_IF_NOT(requirements_satisfied()); StartRecording(); EXPECT_EQ(0, audio_device()->InitPlayout()); @@ -621,7 +782,7 @@ TEST_F(AudioDeviceTest, InitStopInitPlayoutWhileRecording) { // Note that we can't add expectations on audio parameters in EXPECT_CALL // since parameter are not provided in the each callback. We therefore test and // verify the parameters in the fake audio transport implementation instead. -TEST_F(AudioDeviceTest, StartPlayoutVerifyCallbacks) { +TEST_P(AudioDeviceTest, StartPlayoutVerifyCallbacks) { SKIP_TEST_IF_NOT(requirements_satisfied()); MockAudioTransport mock(TransportType::kPlay); mock.HandleCallbacks(event(), nullptr, kNumCallbacks); @@ -635,7 +796,7 @@ TEST_F(AudioDeviceTest, StartPlayoutVerifyCallbacks) { // Start recording and verify that the native audio layer starts providing real // audio samples using the RecordedDataIsAvailable() callback. -TEST_F(AudioDeviceTest, StartRecordingVerifyCallbacks) { +TEST_P(AudioDeviceTest, StartRecordingVerifyCallbacks) { SKIP_TEST_IF_NOT(requirements_satisfied()); MockAudioTransport mock(TransportType::kRecord); mock.HandleCallbacks(event(), nullptr, kNumCallbacks); @@ -650,7 +811,7 @@ TEST_F(AudioDeviceTest, StartRecordingVerifyCallbacks) { // Start playout and recording (full-duplex audio) and verify that audio is // active in both directions. -TEST_F(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) { +TEST_P(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) { SKIP_TEST_IF_NOT(requirements_satisfied()); MockAudioTransport mock(TransportType::kPlayAndRecord); mock.HandleCallbacks(event(), nullptr, kNumCallbacks); @@ -679,7 +840,7 @@ TEST_F(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) { // sequence by running in loopback for a few seconds while measuring the size // (max and average) of the FIFO. The size of the FIFO is increased by the // recording side and decreased by the playout side. -TEST_F(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) { +TEST_P(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) { SKIP_TEST_IF_NOT(requirements_satisfied()); NiceMock mock(TransportType::kPlayAndRecord); FifoAudioStream audio_stream; @@ -716,7 +877,7 @@ TEST_F(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) { // some sort of audio feedback loop. E.g. a headset where the mic is placed // close to the speaker to ensure highest possible echo. It is also recommended // to run the test at highest possible output volume. -TEST_F(AudioDeviceTest, DISABLED_MeasureLoopbackLatency) { +TEST_P(AudioDeviceTest, DISABLED_MeasureLoopbackLatency) { SKIP_TEST_IF_NOT(requirements_satisfied()); NiceMock mock(TransportType::kPlayAndRecord); LatencyAudioStream audio_stream; @@ -739,4 +900,20 @@ TEST_F(AudioDeviceTest, DISABLED_MeasureLoopbackLatency) { audio_stream.PrintResults(); } +#ifdef WEBRTC_WIN +// Test two different audio layers (or rather two different Core Audio +// implementations) for Windows. +INSTANTIATE_TEST_CASE_P( + AudioLayerWin, + AudioDeviceTest, + ::testing::Values(AudioDeviceModule::kPlatformDefaultAudio, + AudioDeviceModule::kWindowsCoreAudio2)); +#else +// For all platforms but Windows, only test the default audio layer. +INSTANTIATE_TEST_CASE_P( + AudioLayer, + AudioDeviceTest, + ::testing::Values(AudioDeviceModule::kPlatformDefaultAudio)); +#endif + } // namespace webrtc diff --git a/modules/audio_device/fine_audio_buffer.cc b/modules/audio_device/fine_audio_buffer.cc index d19b669bf2..bd18359009 100644 --- a/modules/audio_device/fine_audio_buffer.cc +++ b/modules/audio_device/fine_audio_buffer.cc @@ -65,18 +65,27 @@ void FineAudioBuffer::GetPlayoutData(rtc::ArrayView audio_buffer, while (playout_buffer_.size() < audio_buffer.size()) { // Get 10ms decoded audio from WebRTC. The ADB knows about number of // channels; hence we can ask for number of samples per channel here. - audio_device_buffer_->RequestPlayoutData(playout_samples_per_channel_10ms_); - // Append 10ms to the end of the local buffer taking number of channels - // into account. - const size_t num_elements_10ms = - playout_channels_ * playout_samples_per_channel_10ms_; - const size_t written_elements = playout_buffer_.AppendData( - num_elements_10ms, [&](rtc::ArrayView buf) { - const size_t samples_per_channel_10ms = - audio_device_buffer_->GetPlayoutData(buf.data()); - return playout_channels_ * samples_per_channel_10ms; - }); - RTC_DCHECK_EQ(num_elements_10ms, written_elements); + if (audio_device_buffer_->RequestPlayoutData( + playout_samples_per_channel_10ms_) == + static_cast(playout_samples_per_channel_10ms_)) { + // Append 10ms to the end of the local buffer taking number of channels + // into account. + const size_t num_elements_10ms = + playout_channels_ * playout_samples_per_channel_10ms_; + const size_t written_elements = playout_buffer_.AppendData( + num_elements_10ms, [&](rtc::ArrayView buf) { + const size_t samples_per_channel_10ms = + audio_device_buffer_->GetPlayoutData(buf.data()); + return playout_channels_ * samples_per_channel_10ms; + }); + RTC_DCHECK_EQ(num_elements_10ms, written_elements); + } else { + // Provide silence if AudioDeviceBuffer::RequestPlayoutData() fails. + // Can e.g. happen when an AudioTransport has not been registered. + const size_t num_bytes = audio_buffer.size() * sizeof(int16_t); + std::memset(audio_buffer.data(), 0, num_bytes); + return; + } } // Provide the requested number of bytes to the consumer. diff --git a/modules/audio_device/include/audio_device.h b/modules/audio_device/include/audio_device.h index a894df41d6..8b7a5cc44e 100644 --- a/modules/audio_device/include/audio_device.h +++ b/modules/audio_device/include/audio_device.h @@ -28,15 +28,16 @@ class AudioDeviceModule : public rtc::RefCountInterface { enum AudioLayer { kPlatformDefaultAudio = 0, - kWindowsCoreAudio = 2, - kLinuxAlsaAudio = 3, - kLinuxPulseAudio = 4, - kAndroidJavaAudio = 5, - kAndroidOpenSLESAudio = 6, - kAndroidJavaInputAndOpenSLESOutputAudio = 7, - kAndroidAAudioAudio = 8, - kAndroidJavaInputAndAAudioOutputAudio = 9, - kDummyAudio = 10 + kWindowsCoreAudio, + kWindowsCoreAudio2, // experimental + kLinuxAlsaAudio, + kLinuxPulseAudio, + kAndroidJavaAudio, + kAndroidOpenSLESAudio, + kAndroidJavaInputAndOpenSLESOutputAudio, + kAndroidAAudioAudio, + kAndroidJavaInputAndAAudioOutputAudio, + kDummyAudio, }; enum WindowsDeviceType { diff --git a/modules/audio_device/include/audio_device_factory.cc b/modules/audio_device/include/audio_device_factory.cc new file mode 100644 index 0000000000..1895bd091b --- /dev/null +++ b/modules/audio_device/include/audio_device_factory.cc @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_device/include/audio_device_factory.h" + +#if defined(WEBRTC_WIN) +#include "modules/audio_device/win/audio_device_module_win.h" +#include "modules/audio_device/win/core_audio_input_win.h" +#include "modules/audio_device/win/core_audio_output_win.h" +#include "modules/audio_device/win/core_audio_utility_win.h" +#endif + +#include "rtc_base/logging.h" +#include "rtc_base/ptr_util.h" + +namespace webrtc { + +rtc::scoped_refptr +CreateWindowsCoreAudioAudioDeviceModule() { + RTC_DLOG(INFO) << __FUNCTION__; + // Returns NULL if Core Audio is not supported or if COM has not been + // initialized correctly using webrtc_win::ScopedCOMInitializer. + if (!webrtc_win::core_audio_utility::IsSupported()) { + RTC_LOG(LS_ERROR) + << "Unable to create ADM since Core Audio is not supported"; + return nullptr; + } + return CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput( + rtc::MakeUnique(), + rtc::MakeUnique()); +} + +} // namespace webrtc diff --git a/modules/audio_device/include/audio_device_factory.h b/modules/audio_device/include/audio_device_factory.h new file mode 100644 index 0000000000..d6b2cf42c8 --- /dev/null +++ b/modules/audio_device/include/audio_device_factory.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_FACTORY_H_ +#define MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_FACTORY_H_ + +#include "modules/audio_device/include/audio_device.h" + +namespace webrtc { + +// Creates an AudioDeviceModule (ADM) for Windows based on the Core Audio API. +// The creating thread must be a COM thread; otherwise nullptr will be returned. +// Example (assuming webrtc namespace): +// +// public: +// rtc::scoped_refptr CreateAudioDevice() { +// // Tell COM that this thread shall live in the MTA. +// com_initializer_ = rtc::MakeUnique( +// webrtc_win::ScopedCOMInitializer::kMTA); +// if (!com_initializer_->Succeeded()) { +// return nullptr; +// } +// return CreateWindowsCoreAudioAudioDeviceModule(); +// } +// +// private: +// std::unique_ptr com_initializer_; +// +rtc::scoped_refptr CreateWindowsCoreAudioAudioDeviceModule(); + +} // namespace webrtc + +#endif // MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_FACTORY_H_ diff --git a/modules/audio_device/win/audio_device_module_win.cc b/modules/audio_device/win/audio_device_module_win.cc new file mode 100644 index 0000000000..2a9daa1275 --- /dev/null +++ b/modules/audio_device/win/audio_device_module_win.cc @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_device/win/audio_device_module_win.h" + +#include + +#include "modules/audio_device/audio_device_buffer.h" +#include "modules/audio_device/include/audio_device.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/refcountedobject.h" +#include "rtc_base/stringutils.h" +#include "rtc_base/thread_checker.h" + +namespace webrtc { +namespace webrtc_win { +namespace { + +// This class combines a generic instance of an AudioInput and a generic +// instance of an AudioOutput to create an AudioDeviceModule. This is mostly +// done by delegating to the audio input/output with some glue code. This class +// also directly implements some of the AudioDeviceModule methods with dummy +// implementations. +// +// An instance must be created, destroyed and used on one and the same thread, +// i.e., all public methods must also be called on the same thread. A thread +// checker will RTC_DCHECK if any method is called on an invalid thread. +// TODO(henrika): is thread checking needed in AudioInput and AudioOutput? +class WindowsAudioDeviceModule : public AudioDeviceModule { + public: + enum class InitStatus { + OK = 0, + PLAYOUT_ERROR = 1, + RECORDING_ERROR = 2, + OTHER_ERROR = 3, + NUM_STATUSES = 4 + }; + + WindowsAudioDeviceModule(std::unique_ptr audio_input, + std::unique_ptr audio_output) + : input_(std::move(audio_input)), output_(std::move(audio_output)) { + RTC_CHECK(input_); + RTC_CHECK(output_); + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + } + + ~WindowsAudioDeviceModule() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + Terminate(); + } + + WindowsAudioDeviceModule(const WindowsAudioDeviceModule&) = delete; + WindowsAudioDeviceModule& operator=(const WindowsAudioDeviceModule&) = delete; + + int32_t ActiveAudioLayer( + AudioDeviceModule::AudioLayer* audioLayer) const override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + // TODO(henrika): it might be possible to remove this unique signature. + *audioLayer = AudioDeviceModule::kWindowsCoreAudio2; + return 0; + } + + int32_t RegisterAudioCallback(AudioTransport* audioCallback) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK(audio_device_buffer_); + RTC_DCHECK_RUN_ON(&thread_checker_); + return audio_device_buffer_->RegisterAudioCallback(audioCallback); + } + + int32_t Init() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (initialized_) { + return 0; + } + audio_device_buffer_ = rtc::MakeUnique(); + AttachAudioBuffer(); + InitStatus status; + if (output_->Init() != 0) { + status = InitStatus::PLAYOUT_ERROR; + } else if (input_->Init() != 0) { + output_->Terminate(); + status = InitStatus::RECORDING_ERROR; + } else { + initialized_ = true; + status = InitStatus::OK; + } + if (status != InitStatus::OK) { + RTC_LOG(LS_ERROR) << "Audio device initialization failed"; + return -1; + } + return 0; + } + + int32_t Terminate() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) + return 0; + int32_t err = input_->Terminate(); + err |= output_->Terminate(); + initialized_ = false; + RTC_DCHECK_EQ(err, 0); + return err; + } + + bool Initialized() const override { + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_; + } + + int16_t PlayoutDevices() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->NumDevices(); + } + + int16_t RecordingDevices() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->NumDevices(); + } + + int32_t PlayoutDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + std::string name_str, guid_str; + int ret = -1; + if (guid != nullptr) { + ret = output_->DeviceName(index, &name_str, &guid_str); + rtc::strcpyn(guid, kAdmMaxGuidSize, guid_str.c_str()); + } else { + ret = output_->DeviceName(index, &name_str, nullptr); + } + rtc::strcpyn(name, kAdmMaxDeviceNameSize, name_str.c_str()); + return ret; + } + int32_t RecordingDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + std::string name_str, guid_str; + int ret = -1; + if (guid != nullptr) { + ret = input_->DeviceName(index, &name_str, &guid_str); + rtc::strcpyn(guid, kAdmMaxGuidSize, guid_str.c_str()); + } else { + ret = input_->DeviceName(index, &name_str, nullptr); + } + rtc::strcpyn(name, kAdmMaxDeviceNameSize, name_str.c_str()); + return ret; + } + + int32_t SetPlayoutDevice(uint16_t index) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->SetDevice(index); + } + + int32_t SetPlayoutDevice( + AudioDeviceModule::WindowsDeviceType device) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->SetDevice(device); + } + int32_t SetRecordingDevice(uint16_t index) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->SetDevice(index); + } + + int32_t SetRecordingDevice( + AudioDeviceModule::WindowsDeviceType device) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->SetDevice(device); + } + + int32_t PlayoutIsAvailable(bool* available) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = true; + return 0; + } + + int32_t InitPlayout() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->InitPlayout(); + } + + bool PlayoutIsInitialized() const override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->PlayoutIsInitialized(); + } + + int32_t RecordingIsAvailable(bool* available) override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = true; + return 0; + } + + int32_t InitRecording() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->InitRecording(); + } + + bool RecordingIsInitialized() const override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->RecordingIsInitialized(); + } + + int32_t StartPlayout() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->StartPlayout(); + } + + int32_t StopPlayout() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->StopPlayout(); + } + + bool Playing() const override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return output_->Playing(); + } + + int32_t StartRecording() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->StartRecording(); + } + + int32_t StopRecording() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return input_->StopRecording(); + } + + bool Recording() const override { + RTC_LOG(INFO) << __FUNCTION__; + return input_->Recording(); + } + + int32_t InitSpeaker() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DLOG(LS_WARNING) << "This method has no effect"; + return initialized_ ? 0 : -1; + } + + bool SpeakerIsInitialized() const override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DLOG(LS_WARNING) << "This method has no effect"; + return initialized_; + } + + int32_t InitMicrophone() override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DLOG(LS_WARNING) << "This method has no effect"; + return initialized_ ? 0 : -1; + } + + bool MicrophoneIsInitialized() const override { + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DLOG(LS_WARNING) << "This method has no effect"; + return initialized_; + } + + int32_t SpeakerVolumeIsAvailable(bool* available) override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = false; + return 0; + } + + int32_t SetSpeakerVolume(uint32_t volume) override { return 0; } + int32_t SpeakerVolume(uint32_t* volume) const override { return 0; } + int32_t MaxSpeakerVolume(uint32_t* maxVolume) const override { return 0; } + int32_t MinSpeakerVolume(uint32_t* minVolume) const override { return 0; } + + int32_t MicrophoneVolumeIsAvailable(bool* available) override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = false; + return 0; + } + + int32_t SetMicrophoneVolume(uint32_t volume) override { return 0; } + int32_t MicrophoneVolume(uint32_t* volume) const override { return 0; } + int32_t MaxMicrophoneVolume(uint32_t* maxVolume) const override { return 0; } + int32_t MinMicrophoneVolume(uint32_t* minVolume) const override { return 0; } + + int32_t SpeakerMuteIsAvailable(bool* available) override { return 0; } + int32_t SetSpeakerMute(bool enable) override { return 0; } + int32_t SpeakerMute(bool* enabled) const override { return 0; } + + int32_t MicrophoneMuteIsAvailable(bool* available) override { return 0; } + int32_t SetMicrophoneMute(bool enable) override { return 0; } + int32_t MicrophoneMute(bool* enabled) const override { return 0; } + + int32_t StereoPlayoutIsAvailable(bool* available) const override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = true; + return 0; + } + + int32_t SetStereoPlayout(bool enable) override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 0; + } + + int32_t StereoPlayout(bool* enabled) const override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *enabled = true; + return 0; + } + + int32_t StereoRecordingIsAvailable(bool* available) const override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = true; + return 0; + } + + int32_t SetStereoRecording(bool enable) override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 0; + } + + int32_t StereoRecording(bool* enabled) const override { + // TODO(henrika): improve support. + RTC_LOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + *enabled = true; + return 0; + } + + int32_t PlayoutDelay(uint16_t* delayMS) const override { return 0; } + + bool BuiltInAECIsAvailable() const override { return false; } + bool BuiltInAGCIsAvailable() const override { return false; } + bool BuiltInNSIsAvailable() const override { return false; } + + int32_t EnableBuiltInAEC(bool enable) override { return 0; } + int32_t EnableBuiltInAGC(bool enable) override { return 0; } + int32_t EnableBuiltInNS(bool enable) override { return 0; } + + int32_t AttachAudioBuffer() { + RTC_DLOG(INFO) << __FUNCTION__; + output_->AttachAudioBuffer(audio_device_buffer_.get()); + input_->AttachAudioBuffer(audio_device_buffer_.get()); + return 0; + } + + private: + // Ensures that the class is used on the same thread as it is constructed + // and destroyed on. + rtc::ThreadChecker thread_checker_; + + // Implements the AudioInput interface and deals with audio capturing parts. + const std::unique_ptr input_; + + // Implements the AudioOutput interface and deals with audio rendering parts. + const std::unique_ptr output_; + + // The AudioDeviceBuffer (ADB) instance is needed for sending/receiving audio + // to/from the WebRTC layer. Created and owned by this object. Used by + // both |input_| and |output_| but they use orthogonal parts of the ADB. + std::unique_ptr audio_device_buffer_; + + // Set to true after a successful call to Init(). Cleared by Terminate(). + bool initialized_ = false; +}; + +} // namespace + +rtc::scoped_refptr +CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput( + std::unique_ptr audio_input, + std::unique_ptr audio_output) { + RTC_LOG(INFO) << __FUNCTION__; + return new rtc::RefCountedObject( + std::move(audio_input), std::move(audio_output)); +} + +} // namespace webrtc_win +} // namespace webrtc diff --git a/modules/audio_device/win/audio_device_module_win.h b/modules/audio_device/win/audio_device_module_win.h new file mode 100644 index 0000000000..7484ef7f8d --- /dev/null +++ b/modules/audio_device/win/audio_device_module_win.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_AUDIO_DEVICE_WIN_AUDIO_DEVICE_MODULE_WIN_H_ +#define MODULES_AUDIO_DEVICE_WIN_AUDIO_DEVICE_MODULE_WIN_H_ + +#include +#include + +#include "modules/audio_device/include/audio_device.h" +#include "rtc_base/scoped_ref_ptr.h" + +namespace webrtc { + +class AudioDeviceBuffer; + +namespace webrtc_win { + +// This interface represents the main input-related parts of the complete +// AudioDeviceModule interface. +class AudioInput { + public: + virtual ~AudioInput() {} + + virtual int Init() = 0; + virtual int Terminate() = 0; + virtual int NumDevices() const = 0; + virtual int SetDevice(int index) = 0; + virtual int SetDevice(AudioDeviceModule::WindowsDeviceType device) = 0; + virtual int DeviceName(int index, std::string* name, std::string* guid) = 0; + virtual void AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) = 0; + virtual bool RecordingIsInitialized() const = 0; + virtual int InitRecording() = 0; + virtual int StartRecording() = 0; + virtual int StopRecording() = 0; + virtual bool Recording() = 0; + virtual int VolumeIsAvailable(bool* available) = 0; +}; + +// This interface represents the main output-related parts of the complete +// AudioDeviceModule interface. +class AudioOutput { + public: + virtual ~AudioOutput() {} + + virtual int Init() = 0; + virtual int Terminate() = 0; + virtual int NumDevices() const = 0; + virtual int SetDevice(int index) = 0; + virtual int SetDevice(AudioDeviceModule::WindowsDeviceType device) = 0; + virtual int DeviceName(int index, std::string* name, std::string* guid) = 0; + virtual void AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) = 0; + virtual bool PlayoutIsInitialized() const = 0; + virtual int InitPlayout() = 0; + virtual int StartPlayout() = 0; + virtual int StopPlayout() = 0; + virtual bool Playing() = 0; + virtual int VolumeIsAvailable(bool* available) = 0; +}; + +// Combines an AudioInput and an AudioOutput implementation to build an +// AudioDeviceModule. Hides most parts of the full ADM interface. +rtc::scoped_refptr +CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput( + std::unique_ptr audio_input, + std::unique_ptr audio_output); + +} // namespace webrtc_win + +} // namespace webrtc + +#endif // MODULES_AUDIO_DEVICE_WIN_AUDIO_DEVICE_MODULE_WIN_H_ diff --git a/modules/audio_device/win/core_audio_base_win.cc b/modules/audio_device/win/core_audio_base_win.cc new file mode 100644 index 0000000000..cbca15c480 --- /dev/null +++ b/modules/audio_device/win/core_audio_base_win.cc @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_device/win/core_audio_base_win.h" + +#include + +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/win/windows_version.h" + +using Microsoft::WRL::ComPtr; + +namespace webrtc { +namespace webrtc_win { +namespace { + +enum DefaultDeviceType { + kDefault, + kDefaultCommunications, + kDefaultDeviceTypeMaxCount, +}; + +const char* DirectionToString(CoreAudioBase::Direction direction) { + switch (direction) { + case CoreAudioBase::Direction::kOutput: + return "Output"; + case CoreAudioBase::Direction::kInput: + return "Input"; + default: + return "Unkown"; + } +} + +void Run(void* obj) { + RTC_DCHECK(obj); + reinterpret_cast(obj)->ThreadRun(); +} + +} // namespace + +CoreAudioBase::CoreAudioBase(Direction direction, OnDataCallback callback) + : direction_(direction), on_data_callback_(callback), format_() { + RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction) << "]"; + + // Create the event which the audio engine will signal each time a buffer + // becomes ready to be processed by the client. + audio_samples_event_.Set(CreateEvent(nullptr, false, false, nullptr)); + RTC_DCHECK(audio_samples_event_.IsValid()); + + // Event to be be set in Stop() when rendering/capturing shall stop. + stop_event_.Set(CreateEvent(nullptr, false, false, nullptr)); + RTC_DCHECK(stop_event_.IsValid()); +} + +CoreAudioBase::~CoreAudioBase() { + RTC_DLOG(INFO) << __FUNCTION__; +} + +EDataFlow CoreAudioBase::GetDataFlow() const { + return direction_ == CoreAudioBase::Direction::kOutput ? eRender : eCapture; +} + +int CoreAudioBase::NumberOfActiveDevices() const { + return core_audio_utility::NumberOfActiveDevices(GetDataFlow()); +} + +int CoreAudioBase::NumberOfEnumeratedDevices() const { + const int num_active = NumberOfActiveDevices(); + return num_active > 0 ? num_active + kDefaultDeviceTypeMaxCount : 0; +} + +bool CoreAudioBase::IsDefaultDevice(int index) const { + return index == kDefault; +} + +bool CoreAudioBase::IsDefaultCommunicationsDevice(int index) const { + return index == kDefaultCommunications; +} + +bool CoreAudioBase::IsDefaultDevice(const std::string& device_id) const { + return (IsInput() && + (device_id == core_audio_utility::GetDefaultInputDeviceID())) || + (IsOutput() && + (device_id == core_audio_utility::GetDefaultOutputDeviceID())); +} + +bool CoreAudioBase::IsDefaultCommunicationsDevice( + const std::string& device_id) const { + return (IsInput() && + (device_id == + core_audio_utility::GetCommunicationsInputDeviceID())) || + (IsOutput() && + (device_id == core_audio_utility::GetCommunicationsOutputDeviceID())); +} + +bool CoreAudioBase::IsInput() const { + return direction_ == CoreAudioBase::Direction::kInput; +} + +bool CoreAudioBase::IsOutput() const { + return direction_ == CoreAudioBase::Direction::kOutput; +} + +std::string CoreAudioBase::GetDeviceID(int index) const { + if (index >= NumberOfEnumeratedDevices()) { + RTC_LOG(LS_ERROR) << "Invalid device index"; + return std::string(); + } + + std::string device_id; + if (IsDefaultDevice(index)) { + device_id = IsInput() ? core_audio_utility::GetDefaultInputDeviceID() + : core_audio_utility::GetDefaultOutputDeviceID(); + } else if (IsDefaultCommunicationsDevice(index)) { + device_id = IsInput() + ? core_audio_utility::GetCommunicationsInputDeviceID() + : core_audio_utility::GetCommunicationsOutputDeviceID(); + } else { + AudioDeviceNames device_names; + bool ok = IsInput() + ? core_audio_utility::GetInputDeviceNames(&device_names) + : core_audio_utility::GetOutputDeviceNames(&device_names); + if (ok) { + device_id = device_names[index].unique_id; + } + } + return device_id; +} + +int CoreAudioBase::DeviceName(int index, + std::string* name, + std::string* guid) const { + RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction()) + << "]"; + if (index > NumberOfEnumeratedDevices() - 1) { + RTC_LOG(LS_ERROR) << "Invalid device index"; + return -1; + } + + AudioDeviceNames device_names; + bool ok = IsInput() ? core_audio_utility::GetInputDeviceNames(&device_names) + : core_audio_utility::GetOutputDeviceNames(&device_names); + if (!ok) { + RTC_LOG(LS_ERROR) << "Failed to get the device name"; + return -1; + } + + *name = device_names[index].device_name; + RTC_DLOG(INFO) << "name: " << *name; + if (guid != nullptr) { + *guid = device_names[index].unique_id; + RTC_DLOG(INFO) << "guid: " << guid; + } + return 0; +} + +bool CoreAudioBase::Init() { + RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction()) + << "]"; + RTC_DCHECK(!device_id_.empty()); + RTC_DCHECK(audio_device_buffer_); + RTC_DCHECK(!audio_client_.Get()); + + // Use an existing |device_id_| and set parameters which are required to + // create an audio client. It is up to the parent class to set |device_id_|. + // TODO(henrika): improve device notification. + std::string device_id = device_id_; + ERole role = eConsole; + if (IsDefaultDevice(device_id)) { + device_id = AudioDeviceName::kDefaultDeviceId; + role = eConsole; + } else if (IsDefaultCommunicationsDevice(device_id)) { + device_id = AudioDeviceName::kDefaultCommunicationsDeviceId; + role = eCommunications; + } + + // Create an IAudioClient interface which enables us to create and initialize + // an audio stream between an audio application and the audio engine. + ComPtr audio_client = + core_audio_utility::CreateClient(device_id, GetDataFlow(), role); + if (!audio_client.Get()) { + return false; + } + + // Retrieve preferred audio input or output parameters for the given client. + AudioParameters params; + if (FAILED(core_audio_utility::GetPreferredAudioParameters(audio_client.Get(), + ¶ms))) { + return false; + } + + // Define the output WAVEFORMATEXTENSIBLE format in |format_|. + WAVEFORMATEX* format = &format_.Format; + format->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + format->nChannels = rtc::dchecked_cast(params.channels()); + format->nSamplesPerSec = params.sample_rate(); + format->wBitsPerSample = rtc::dchecked_cast(params.bits_per_sample()); + format->nBlockAlign = (format->wBitsPerSample / 8) * format->nChannels; + format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign; + format->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + // Add the parts which are unique for the WAVE_FORMAT_EXTENSIBLE structure. + format_.Samples.wValidBitsPerSample = + rtc::dchecked_cast(params.bits_per_sample()); + // TODO(henrika): improve (common for input and output?) + format_.dwChannelMask = params.channels() == 1 + ? SPEAKER_FRONT_CENTER + : SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + RTC_DLOG(INFO) << core_audio_utility::WaveFormatExToString(&format_); + + // Verify that the format is supported. + if (!core_audio_utility::IsFormatSupported( + audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &format_)) { + return false; + } + + // Initialize the audio stream between the client and the device in shared + // mode using event-driven buffer handling. + if (FAILED(core_audio_utility::SharedModeInitialize( + audio_client.Get(), &format_, audio_samples_event_, + &endpoint_buffer_size_frames_))) { + return false; + } + + // Check device period and the preferred buffer size and log a warning if + // WebRTC's buffer size is not an even divisor of the preferred buffer size + // in Core Audio. + // TODO(henrik): sort out if a non-perfect match really is an issue. + REFERENCE_TIME device_period; + if (FAILED(core_audio_utility::GetDevicePeriod( + audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &device_period))) { + return false; + } + const double device_period_in_seconds = + static_cast( + core_audio_utility::ReferenceTimeToTimeDelta(device_period).ms()) / + 1000.0L; + const int preferred_frames_per_buffer = + static_cast(params.sample_rate() * device_period_in_seconds + 0.5); + RTC_DLOG(INFO) << "preferred_frames_per_buffer: " + << preferred_frames_per_buffer; + if (preferred_frames_per_buffer % params.frames_per_buffer()) { + RTC_LOG(WARNING) << "Buffer size of " << params.frames_per_buffer() + << " is not an even divisor of " + << preferred_frames_per_buffer; + } + + // Store valid COM interfaces. + audio_client_ = audio_client; + + return true; +} + +bool CoreAudioBase::Start() { + RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction()) + << "]"; + + audio_thread_ = rtc::MakeUnique( + Run, this, IsInput() ? "wasapi_capture_thread" : "wasapi_render_thread", + rtc::kRealtimePriority); + audio_thread_->Start(); + if (!audio_thread_->IsRunning()) { + StopThread(); + RTC_LOG(LS_ERROR) << "Failed to start audio thread"; + return false; + } + RTC_DLOG(INFO) << "Started thread with name: " << audio_thread_->name(); + + // Start streaming data between the endpoint buffer and the audio engine. + _com_error error = audio_client_->Start(); + if (error.Error() != S_OK) { + StopThread(); + RTC_LOG(LS_ERROR) << "IAudioClient::Start failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + return true; +} + +bool CoreAudioBase::Stop() { + RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction()) + << "]"; + + // Stop streaming and the internal audio thread. + _com_error error = audio_client_->Stop(); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioClient::Stop failed: " + << core_audio_utility::ErrorToString(error); + } + StopThread(); + + // Flush all pending data and reset the audio clock stream position to 0. + error = audio_client_->Reset(); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioClient::Reset failed: " + << core_audio_utility::ErrorToString(error); + } + + if (IsOutput()) { + // Extra safety check to ensure that the buffers are cleared. + // If the buffers are not cleared correctly, the next call to Start() + // would fail with AUDCLNT_E_BUFFER_ERROR at + // IAudioRenderClient::GetBuffer(). + UINT32 num_queued_frames = 0; + audio_client_->GetCurrentPadding(&num_queued_frames); + RTC_DCHECK_EQ(0u, num_queued_frames); + } + + return true; +} + +bool CoreAudioBase::IsVolumeControlAvailable(bool* available) const { + // A valid IAudioClient is required to access the ISimpleAudioVolume interface + // properly. It is possible to use IAudioSessionManager::GetSimpleAudioVolume + // as well but we use the audio client here to ensure that the initialized + // audio session is visible under group box labeled "Applications" in + // Sndvol.exe. + if (!audio_client_.Get()) { + return false; + } + + // Try to create an ISimpleAudioVolume instance. + ComPtr audio_volume = + core_audio_utility::CreateSimpleAudioVolume(audio_client_.Get()); + if (!audio_volume.Get()) { + RTC_DLOG(LS_ERROR) << "Volume control is not supported"; + return false; + } + + // Try to use the valid volume control. + float volume = 0.0; + _com_error error = audio_volume->GetMasterVolume(&volume); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "ISimpleAudioVolume::GetMasterVolume failed: " + << core_audio_utility::ErrorToString(error); + *available = false; + } + RTC_DLOG(INFO) << "master volume for output audio session: " << volume; + + *available = true; + return false; +} + +void CoreAudioBase::StopThread() { + RTC_DLOG(INFO) << __FUNCTION__; + if (audio_thread_) { + if (audio_thread_->IsRunning()) { + RTC_DLOG(INFO) << "Sets stop_event..."; + SetEvent(stop_event_.Get()); + RTC_DLOG(INFO) << "PlatformThread::Stop..."; + audio_thread_->Stop(); + } + audio_thread_.reset(); + + // Ensure that we don't quit the main thread loop immediately next + // time Start() is called. + ResetEvent(stop_event_.Get()); + } +} + +void CoreAudioBase::ThreadRun() { + if (!core_audio_utility::IsMMCSSSupported()) { + RTC_LOG(LS_ERROR) << "MMCSS is not supported"; + return; + } + RTC_DLOG(INFO) << "ThreadRun starts..."; + // TODO(henrika): difference between "Pro Audio" and "Audio"? + ScopedMMCSSRegistration mmcss_registration(L"Pro Audio"); + ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA); + RTC_DCHECK(mmcss_registration.Succeeded()); + RTC_DCHECK(com_initializer.Succeeded()); + RTC_DCHECK(stop_event_.IsValid()); + RTC_DCHECK(audio_samples_event_.IsValid()); + + bool streaming = true; + bool error = false; + HANDLE wait_array[] = {stop_event_.Get(), audio_samples_event_.Get()}; + + // The device frequency is the frequency generated by the hardware clock in + // the audio device. The GetFrequency() method reports a constant frequency. + UINT64 device_frequency = 0; + if (audio_clock_.Get()) { + RTC_DCHECK(IsOutput()); + _com_error result = audio_clock_->GetFrequency(&device_frequency); + if ((error = result.Error()) != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioClock::GetFrequency failed: " + << core_audio_utility::ErrorToString(error); + } + } + + // Keep streaming audio until the stop event or the stream-switch event + // is signaled. An error event can also break the main thread loop. + while (streaming && !error) { + // Wait for a close-down event, stream-switch event or a new render event. + DWORD wait_result = WaitForMultipleObjects(arraysize(wait_array), + wait_array, false, INFINITE); + switch (wait_result) { + case WAIT_OBJECT_0 + 0: + // |stop_event_| has been set. + streaming = false; + break; + case WAIT_OBJECT_0 + 1: + // |audio_samples_event_| has been set. + error = !on_data_callback_(device_frequency); + break; + default: + error = true; + break; + } + } + + if (streaming && error) { + RTC_LOG(LS_ERROR) << "WASAPI streaming failed."; + // Stop audio streaming since something has gone wrong in our main thread + // loop. Note that, we are still in a "started" state, hence a Stop() call + // is required to join the thread properly. + audio_client_->Stop(); + + // TODO(henrika): notify clients that something has gone wrong and that + // this stream should be destroyed instead of reused in the future. + } + + RTC_DLOG(INFO) << "...ThreadRun stops"; +} + +} // namespace webrtc_win +} // namespace webrtc diff --git a/modules/audio_device/win/core_audio_base_win.h b/modules/audio_device/win/core_audio_base_win.h new file mode 100644 index 0000000000..b0886f99d3 --- /dev/null +++ b/modules/audio_device/win/core_audio_base_win.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_ +#define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_ + +#include +#include +#include + +#include "modules/audio_device/win/core_audio_utility_win.h" +#include "rtc_base/platform_thread.h" +#include "rtc_base/thread_checker.h" + +namespace webrtc { + +class AudioDeviceBuffer; +class FineAudioBuffer; + +namespace webrtc_win { + +// Serves as base class for CoreAudioInput and CoreAudioOutput and supports +// device handling and audio streaming where the direction (input or output) +// is set at constructions by the parent. +class CoreAudioBase { + public: + enum class Direction { + kInput, + kOutput, + }; + + // Callback definition for notifications of new audio data. For input clients, + // it means that "new audio data has now been captured", and for output + // clients, "the output layer now needs new audio data". + typedef std::function OnDataCallback; + + explicit CoreAudioBase(Direction direction, OnDataCallback callback); + ~CoreAudioBase(); + + std::string GetDeviceID(int index) const; + int DeviceName(int index, std::string* name, std::string* guid) const; + + bool Init(); + bool Start(); + bool Stop(); + bool IsVolumeControlAvailable(bool* available) const; + + Direction direction() const { return direction_; } + + void ThreadRun(); + + CoreAudioBase(const CoreAudioBase&) = delete; + CoreAudioBase& operator=(const CoreAudioBase&) = delete; + + protected: + // Returns number of active devices given the specified |direction_|. + int NumberOfActiveDevices() const; + + // Returns total number of enumerated audio devices which is the sum of all + // active devices plus two extra (one default and one default + // communications). The value in |direction_| determines if capture or + // render devices are counted. + int NumberOfEnumeratedDevices() const; + + bool IsInput() const; + bool IsOutput() const; + bool IsDefaultDevice(int index) const; + bool IsDefaultCommunicationsDevice(int index) const; + bool IsDefaultDevice(const std::string& device_id) const; + bool IsDefaultCommunicationsDevice(const std::string& device_id) const; + EDataFlow GetDataFlow() const; + + rtc::ThreadChecker thread_checker_; + rtc::ThreadChecker thread_checker_audio_; + const Direction direction_; + const OnDataCallback on_data_callback_; + AudioDeviceBuffer* audio_device_buffer_ = nullptr; + bool initialized_ = false; + std::string device_id_; + WAVEFORMATEXTENSIBLE format_ = {}; + uint32_t endpoint_buffer_size_frames_ = 0; + Microsoft::WRL::ComPtr audio_client_; + Microsoft::WRL::ComPtr audio_clock_; + ScopedHandle audio_samples_event_; + ScopedHandle stop_event_; + std::unique_ptr audio_thread_; + + private: + void StopThread(); +}; + +} // namespace webrtc_win +} // namespace webrtc + +#endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_ diff --git a/modules/audio_device/win/core_audio_input_win.cc b/modules/audio_device/win/core_audio_input_win.cc new file mode 100644 index 0000000000..1322f1cafb --- /dev/null +++ b/modules/audio_device/win/core_audio_input_win.cc @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_device/win/core_audio_input_win.h" + +#include "modules/audio_device/audio_device_buffer.h" +#include "modules/audio_device/fine_audio_buffer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/ptr_util.h" + +using Microsoft::WRL::ComPtr; + +namespace webrtc { +namespace webrtc_win { + +CoreAudioInput::CoreAudioInput() + : CoreAudioBase(CoreAudioBase::Direction::kInput, + [this](uint64_t freq) { return OnDataCallback(freq); }) { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + thread_checker_audio_.DetachFromThread(); +} + +CoreAudioInput::~CoreAudioInput() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); +} + +int CoreAudioInput::Init() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + StopRecording(); + return 0; +} + +int CoreAudioInput::Terminate() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 0; +} + +int CoreAudioInput::NumDevices() const { + RTC_DCHECK_RUN_ON(&thread_checker_); + return core_audio_utility::NumberOfActiveDevices(eCapture); +} + +int CoreAudioInput::SetDevice(int index) { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << index; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (initialized_) { + return -1; + } + + std::string device_id = GetDeviceID(index); + RTC_DLOG(INFO) << "index=" << index << " => device_id: " << device_id; + device_id_ = device_id; + + return device_id_.empty() ? -1 : 0; +} + +int CoreAudioInput::SetDevice(AudioDeviceModule::WindowsDeviceType device) { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << device; + RTC_DCHECK_RUN_ON(&thread_checker_); + return SetDevice((device == AudioDeviceModule::kDefaultDevice) ? 0 : 1); +} + +int CoreAudioInput::DeviceName(int index, + std::string* name, + std::string* guid) { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << index; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DCHECK(name); + return CoreAudioBase::DeviceName(index, name, guid); +} + +void CoreAudioInput::AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + audio_device_buffer_ = audio_buffer; +} + +bool CoreAudioInput::RecordingIsInitialized() const { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << initialized_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_; +} + +int CoreAudioInput::InitRecording() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DCHECK(!initialized_); + RTC_DCHECK(!Recording()); + RTC_DCHECK(!audio_client_.Get()); + RTC_DCHECK(!audio_capture_client_.Get()); + + // Create an IAudioClient and store the valid interface pointer in + // |audio_client_|. The base class will use optimal input parameters and do + // an event driven shared mode initialization. The utilized format will be + // stored in |format_| and can be used for configuration and allocation of + // audio buffers. + if (!CoreAudioBase::Init()) { + return -1; + } + RTC_DCHECK(audio_client_.Get()); + + // Configure the recording side of the audio device buffer using |format_| + // after a trivial sanity check of the format structure. + RTC_DCHECK(audio_device_buffer_); + WAVEFORMATEX* format = &format_.Format; + RTC_DCHECK_EQ(format->wFormatTag, WAVE_FORMAT_EXTENSIBLE); + audio_device_buffer_->SetRecordingSampleRate(format->nSamplesPerSec); + audio_device_buffer_->SetRecordingChannels(format->nChannels); + + // Create a modified audio buffer class which allows us to supply any number + // of samples (and not only multiple of 10ms) to match the optimal buffer + // size per callback used by Core Audio. + // TODO(henrika): can we share one FineAudioBuffer? + fine_audio_buffer_ = rtc::MakeUnique(audio_device_buffer_); + + // Create an IAudioCaptureClient for an initialized IAudioClient. + // The IAudioCaptureClient interface enables a client to read input data from + // a capture endpoint buffer. + ComPtr audio_capture_client = + core_audio_utility::CreateCaptureClient(audio_client_.Get()); + if (!audio_capture_client.Get()) { + return -1; + } + + // Query performance frequency. + LARGE_INTEGER ticks_per_sec = {}; + qpc_to_100ns_.reset(); + if (::QueryPerformanceFrequency(&ticks_per_sec)) { + double qpc_ticks_per_second = + rtc::dchecked_cast(ticks_per_sec.QuadPart); + qpc_to_100ns_ = 10000000.0 / qpc_ticks_per_second; + } + + // Store valid COM interfaces. Note that, |audio_client_| has already been + // set in CoreAudioBase::Init(). + audio_capture_client_ = audio_capture_client; + + initialized_ = true; + return 0; +} + +int CoreAudioInput::StartRecording() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DCHECK(!Recording()); + if (!initialized_) { + RTC_DLOG(LS_WARNING) + << "Recording can not start since InitRecording must succeed first"; + return 0; + } + if (fine_audio_buffer_) { + fine_audio_buffer_->ResetRecord(); + } + + if (!Start()) { + return -1; + } + + return 0; +} + +int CoreAudioInput::StopRecording() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return 0; + } + + // Release resources allocated in InitRecording() and then return if this + // method is called without any active input audio. + if (!Recording()) { + RTC_DLOG(WARNING) << "No input stream is active"; + audio_client_.Reset(); + audio_capture_client_.Reset(); + initialized_ = false; + return 0; + } + + if (!Stop()) { + RTC_LOG(LS_ERROR) << "StopRecording failed"; + return -1; + } + + // TODO(henrika): if we want to support Init(), Start(), Stop(), Init(), + // Start(), Stop() without close in between, these lines are needed. + // Not supported on mobile ADMs, hence we can probably live without it. + // audio_client_.Reset(); + // audio_capture_client_.Reset(); + // audio_device_buffer_->NativeAudioRecordingInterrupted(); + thread_checker_audio_.DetachFromThread(); + qpc_to_100ns_.reset(); + initialized_ = false; + return 0; +} + +bool CoreAudioInput::Recording() { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << (audio_thread_ != nullptr); + RTC_DCHECK_RUN_ON(&thread_checker_); + return audio_thread_ != nullptr; +} + +// TODO(henrika): finalize support of audio session volume control. As is, we +// are not compatible with the old ADM implementation since it allows accessing +// the volume control with any active audio output stream. +int CoreAudioInput::VolumeIsAvailable(bool* available) { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return IsVolumeControlAvailable(available) ? 0 : -1; +} + +bool CoreAudioInput::OnDataCallback(uint64_t device_frequency) { + RTC_DCHECK_RUN_ON(&thread_checker_audio_); + UINT32 num_frames_in_next_packet = 0; + _com_error error = + audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetNextPacketSize failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + // Drain the WASAPI capture buffer fully if audio has been recorded. + while (num_frames_in_next_packet > 0) { + uint8_t* audio_data; + UINT32 num_frames_to_read = 0; + DWORD flags = 0; + UINT64 device_position_frames = 0; + UINT64 capture_time_100ns = 0; + error = audio_capture_client_->GetBuffer(&audio_data, &num_frames_to_read, + &flags, &device_position_frames, + &capture_time_100ns); + if (error.Error() == AUDCLNT_S_BUFFER_EMPTY) { + // The call succeeded but no capture data is available to be read. + // Return and start waiting for new capture event + RTC_DCHECK_EQ(num_frames_to_read, 0u); + return true; + } + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetBuffer failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + // TODO(henrika): only update the latency estimate N times per second to + // save resources. + // TODO(henrika): note that FineAudioBuffer adds latency as well. + auto opt_record_delay_ms = EstimateLatencyMillis(capture_time_100ns); + + // The data in the packet is not correlated with the previous packet's + // device position; possibly due to a stream state transition or timing + // glitch. The behavior of the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY flag + // is undefined on the application's first call to GetBuffer after Start. + if (device_position_frames != 0 && + flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) { + RTC_DLOG(LS_WARNING) << "AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY"; + } + // The time at which the device's stream position was recorded is uncertain. + // Thus, the client might be unable to accurately set a time stamp for the + // current data packet. + if (flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) { + RTC_DLOG(LS_WARNING) << "AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR"; + } + + // Treat all of the data in the packet as silence and ignore the actual + // data values when AUDCLNT_BUFFERFLAGS_SILENT is set. + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { + rtc::ExplicitZeroMemory(audio_data, + format_.Format.nBlockAlign * num_frames_to_read); + RTC_DLOG(LS_WARNING) << "Captured audio is replaced by silence"; + } else { + // Copy recorded audio in |audio_data| to the WebRTC sink using the + // FineAudioBuffer object. + // TODO(henrika): fix delay estimation. + int record_delay_ms = 0; + if (opt_record_delay_ms) { + record_delay_ms = *opt_record_delay_ms; + // RTC_DLOG(INFO) << "record_delay_ms: " << record_delay_ms; + } + fine_audio_buffer_->DeliverRecordedData( + rtc::MakeArrayView(reinterpret_cast(audio_data), + format_.Format.nChannels * num_frames_to_read), + + record_delay_ms); + } + + error = audio_capture_client_->ReleaseBuffer(num_frames_to_read); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioCaptureClient::ReleaseBuffer failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + error = + audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetNextPacketSize failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + } + + return true; +} + +rtc::Optional CoreAudioInput::EstimateLatencyMillis( + uint64_t capture_time_100ns) { + if (!qpc_to_100ns_) { + return rtc::nullopt; + } + // Input parameter |capture_time_100ns| contains the performance counter at + // the time that the audio endpoint device recorded the device position of + // the first audio frame in the data packet converted into 100ns units. + // We derive a delay estimate by: + // - sampling the current performance counter (qpc_now_raw), + // - converting it into 100ns time units (now_time_100ns), and + // - subtracting |capture_time_100ns| from now_time_100ns. + LARGE_INTEGER perf_counter_now = {}; + if (!::QueryPerformanceCounter(&perf_counter_now)) { + return rtc::nullopt; + } + uint64_t qpc_now_raw = perf_counter_now.QuadPart; + uint64_t now_time_100ns = qpc_now_raw * (*qpc_to_100ns_); + webrtc::TimeDelta delay_us = + webrtc::TimeDelta::us(0.1 * (now_time_100ns - capture_time_100ns) + 0.5); + return delay_us.ms(); +} + +} // namespace webrtc_win +} // namespace webrtc diff --git a/modules/audio_device/win/core_audio_input_win.h b/modules/audio_device/win/core_audio_input_win.h new file mode 100644 index 0000000000..5cfbf2aa0a --- /dev/null +++ b/modules/audio_device/win/core_audio_input_win.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_INPUT_WIN_H_ +#define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_INPUT_WIN_H_ + +#include +#include + +#include "api/optional.h" +#include "modules/audio_device/win/audio_device_module_win.h" +#include "modules/audio_device/win/core_audio_base_win.h" + +namespace webrtc { + +class AudioDeviceBuffer; +class FineAudioBuffer; + +namespace webrtc_win { + +// Windows specific AudioInput implementation using a CoreAudioBase class where +// an input direction is set at construction. Supports capture device handling +// and streaming of captured audio to a WebRTC client. +class CoreAudioInput final : public CoreAudioBase, public AudioInput { + public: + CoreAudioInput(); + ~CoreAudioInput() override; + + // AudioInput implementation. + int Init() override; + int Terminate() override; + int NumDevices() const override; + int SetDevice(int index) override; + int SetDevice(AudioDeviceModule::WindowsDeviceType device) override; + int DeviceName(int index, std::string* name, std::string* guid) override; + void AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) override; + bool RecordingIsInitialized() const override; + int InitRecording() override; + int StartRecording() override; + int StopRecording() override; + bool Recording() override; + int VolumeIsAvailable(bool* available) override; + + CoreAudioInput(const CoreAudioInput&) = delete; + CoreAudioInput& operator=(const CoreAudioInput&) = delete; + + private: + bool OnDataCallback(uint64_t device_frequency); + rtc::Optional EstimateLatencyMillis(uint64_t capture_time_100ns); + + std::unique_ptr fine_audio_buffer_; + Microsoft::WRL::ComPtr audio_capture_client_; + rtc::Optional qpc_to_100ns_; +}; + +} // namespace webrtc_win + +} // namespace webrtc + +#endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_INPUT_WIN_H_ diff --git a/modules/audio_device/win/core_audio_output_win.cc b/modules/audio_device/win/core_audio_output_win.cc new file mode 100644 index 0000000000..48a97677fa --- /dev/null +++ b/modules/audio_device/win/core_audio_output_win.cc @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_device/win/core_audio_output_win.h" + +#include "modules/audio_device/audio_device_buffer.h" +#include "modules/audio_device/fine_audio_buffer.h" +#include "rtc_base/bind.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/ptr_util.h" + +using Microsoft::WRL::ComPtr; + +namespace webrtc { +namespace webrtc_win { + +CoreAudioOutput::CoreAudioOutput() + : CoreAudioBase(CoreAudioBase::Direction::kOutput, + [this](uint64_t freq) { return OnDataCallback(freq); }) { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + thread_checker_audio_.DetachFromThread(); +} + +CoreAudioOutput::~CoreAudioOutput() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + Terminate(); +} + +int CoreAudioOutput::Init() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 0; +} + +int CoreAudioOutput::Terminate() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + StopPlayout(); + return 0; +} + +int CoreAudioOutput::NumDevices() const { + RTC_DCHECK_RUN_ON(&thread_checker_); + return core_audio_utility::NumberOfActiveDevices(eRender); +} + +int CoreAudioOutput::SetDevice(int index) { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << index; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (initialized_) { + return -1; + } + + std::string device_id = GetDeviceID(index); + RTC_DLOG(INFO) << "index=" << index << " => device_id: " << device_id; + device_id_ = device_id; + + return device_id_.empty() ? -1 : 0; +} + +int CoreAudioOutput::SetDevice(AudioDeviceModule::WindowsDeviceType device) { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << device; + RTC_DCHECK_RUN_ON(&thread_checker_); + return SetDevice((device == AudioDeviceModule::kDefaultDevice) ? 0 : 1); +} + +int CoreAudioOutput::DeviceName(int index, + std::string* name, + std::string* guid) { + RTC_DLOG(INFO) << __FUNCTION__ << ": " << index; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DCHECK(name); + return CoreAudioBase::DeviceName(index, name, guid); +} + +void CoreAudioOutput::AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + audio_device_buffer_ = audio_buffer; +} + +bool CoreAudioOutput::PlayoutIsInitialized() const { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_; +} + +int CoreAudioOutput::InitPlayout() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DCHECK(!initialized_); + RTC_DCHECK(!Playing()); + RTC_DCHECK(!audio_client_.Get()); + RTC_DCHECK(!audio_render_client_.Get()); + + // Create an IAudioClient client and store the valid interface pointer in + // |audio_client_|. The base class will use optimal output parameters and do + // an event driven shared mode initialization. The utilized format will be + // stored in |format_| and can be used for configuration and allocation of + // audio buffers. + if (!CoreAudioBase::Init()) { + return -1; + } + RTC_DCHECK(audio_client_.Get()); + + // Configure the playout side of the audio device buffer using |format_| + // after a trivial sanity check of the format structure. + RTC_DCHECK(audio_device_buffer_); + WAVEFORMATEX* format = &format_.Format; + RTC_DCHECK_EQ(format->wFormatTag, WAVE_FORMAT_EXTENSIBLE); + audio_device_buffer_->SetPlayoutSampleRate(format->nSamplesPerSec); + audio_device_buffer_->SetPlayoutChannels(format->nChannels); + + // Create a modified audio buffer class which allows us to ask for any number + // of samples (and not only multiple of 10ms) to match the optimal + // buffer size per callback used by Core Audio. + // TODO(henrika): can we use a shared buffer instead? + fine_audio_buffer_ = rtc::MakeUnique(audio_device_buffer_); + + // Create an IAudioRenderClient for an initialized IAudioClient. + // The IAudioRenderClient interface enables us to write output data to + // a rendering endpoint buffer. + ComPtr audio_render_client = + core_audio_utility::CreateRenderClient(audio_client_.Get()); + if (!audio_render_client.Get()) + return -1; + + ComPtr audio_clock = + core_audio_utility::CreateAudioClock(audio_client_.Get()); + if (!audio_clock.Get()) + return -1; + + // Store valid COM interfaces. Note that, |audio_client_| has already been + // set in CoreAudioBase::Init(). + audio_render_client_ = audio_render_client; + audio_clock_ = audio_clock; + + initialized_ = true; + return 0; +} + +int CoreAudioOutput::StartPlayout() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + RTC_DCHECK(!Playing()); + if (!initialized_) { + RTC_DLOG(LS_WARNING) + << "Playout can not start since InitPlayout must succeed first"; + return 0; + } + if (fine_audio_buffer_) { + fine_audio_buffer_->ResetPlayout(); + } + + if (!core_audio_utility::FillRenderEndpointBufferWithSilence( + audio_client_.Get(), audio_render_client_.Get())) { + RTC_LOG(LS_WARNING) << "Failed to prepare output endpoint with silence"; + } + + num_frames_written_ = endpoint_buffer_size_frames_; + + if (!Start()) { + return -1; + } + + return 0; +} + +int CoreAudioOutput::StopPlayout() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return 0; + } + + // Release resources allocated in InitPlayout() and then return if this + // method is called without any active output audio. + if (!Playing()) { + RTC_DLOG(WARNING) << "No output stream is active"; + audio_client_.Reset(); + audio_render_client_.Reset(); + initialized_ = false; + return 0; + } + + if (!Stop()) { + RTC_LOG(LS_ERROR) << "StopPlayout failed"; + return -1; + } + + thread_checker_audio_.DetachFromThread(); + initialized_ = false; + return 0; +} + +bool CoreAudioOutput::Playing() { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return audio_thread_ != nullptr; +} + +// TODO(henrika): finalize support of audio session volume control. As is, we +// are not compatible with the old ADM implementation since it allows accessing +// the volume control with any active audio output stream. +int CoreAudioOutput::VolumeIsAvailable(bool* available) { + RTC_DLOG(INFO) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + return IsVolumeControlAvailable(available) ? 0 : -1; +} + +bool CoreAudioOutput::OnDataCallback(uint64_t device_frequency) { + RTC_DCHECK_RUN_ON(&thread_checker_audio_); + // Get the padding value which indicates the amount of valid unread data that + // the endpoint buffer currently contains. + UINT32 num_unread_frames = 0; + _com_error error = audio_client_->GetCurrentPadding(&num_unread_frames); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + // Contains how much new data we can write to the buffer without the risk of + // overwriting previously written data that the audio engine has not yet read + // from the buffer. I.e., it is the maximum buffer size we can request when + // calling IAudioRenderClient::GetBuffer(). + UINT32 num_requested_frames = + endpoint_buffer_size_frames_ - num_unread_frames; + + // Request all available space in the rendering endpoint buffer into which the + // client can later write an audio packet. + uint8_t* audio_data; + error = audio_render_client_->GetBuffer(num_requested_frames, &audio_data); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + // TODO(henrika): only update the latency estimate N times per second to + // save resources. + // TODO(henrika): note that FineAudioBuffer adds latency as well. + int playout_delay_ms = EstimateOutputLatencyMillis(device_frequency); + // RTC_DLOG(INFO) << "playout_delay_ms: " << playout_delay_ms; + + // Get audio data from WebRTC and write it to the allocated buffer in + // |audio_data|. + fine_audio_buffer_->GetPlayoutData( + rtc::MakeArrayView(reinterpret_cast(audio_data), + num_requested_frames * format_.Format.nChannels), + playout_delay_ms); + + // Release the buffer space acquired in IAudioRenderClient::GetBuffer. + error = audio_render_client_->ReleaseBuffer(num_requested_frames, 0); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: " + << core_audio_utility::ErrorToString(error); + return false; + } + + num_frames_written_ += num_requested_frames; + + return true; +} + +// TODO(henrika): IAudioClock2::GetDevicePosition could perhaps be used here +// instead. Tried it once, but it crashed for capture devices. +int CoreAudioOutput::EstimateOutputLatencyMillis(uint64_t device_frequency) { + UINT64 position = 0; + UINT64 qpc_position = 0; + int delay_ms = 0; + // Get the device position through output parameter |position|. This is the + // stream position of the sample that is currently playing through the + // speakers. + _com_error error = audio_clock_->GetPosition(&position, &qpc_position); + if (error.Error() == S_OK) { + // Number of frames already played out through the speaker. + const uint64_t num_played_out_frames = + format_.Format.nSamplesPerSec * position / device_frequency; + + // Number of frames that have been written to the buffer but not yet + // played out corresponding to the estimated latency measured in number + // of audio frames. + const uint64_t delay_frames = num_frames_written_ - num_played_out_frames; + + // Convert latency in number of frames into milliseconds. + webrtc::TimeDelta delay = webrtc::TimeDelta::us( + delay_frames * kNumMicrosecsPerSec / format_.Format.nSamplesPerSec); + delay_ms = delay.ms(); + } + return delay_ms; +} + +} // namespace webrtc_win + +} // namespace webrtc diff --git a/modules/audio_device/win/core_audio_output_win.h b/modules/audio_device/win/core_audio_output_win.h new file mode 100644 index 0000000000..c947f3dd81 --- /dev/null +++ b/modules/audio_device/win/core_audio_output_win.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_OUTPUT_WIN_H_ +#define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_OUTPUT_WIN_H_ + +#include +#include + +#include "modules/audio_device/win/audio_device_module_win.h" +#include "modules/audio_device/win/core_audio_base_win.h" + +namespace webrtc { + +class AudioDeviceBuffer; +class FineAudioBuffer; + +namespace webrtc_win { + +// Windows specific AudioOutput implementation using a CoreAudioBase class where +// an output direction is set at construction. Supports render device handling +// and streaming of decoded audio from a WebRTC client to the native audio +// layer. +class CoreAudioOutput final : public CoreAudioBase, public AudioOutput { + public: + CoreAudioOutput(); + ~CoreAudioOutput() override; + + // AudioOutput implementation. + int Init() override; + int Terminate() override; + int NumDevices() const override; + int SetDevice(int index) override; + int SetDevice(AudioDeviceModule::WindowsDeviceType device) override; + int DeviceName(int index, std::string* name, std::string* guid) override; + void AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) override; + bool PlayoutIsInitialized() const override; + int InitPlayout() override; + int StartPlayout() override; + int StopPlayout() override; + bool Playing() override; + int VolumeIsAvailable(bool* available) override; + + CoreAudioOutput(const CoreAudioOutput&) = delete; + CoreAudioOutput& operator=(const CoreAudioOutput&) = delete; + + private: + bool OnDataCallback(uint64_t device_frequency); + int EstimateOutputLatencyMillis(uint64_t device_frequency); + + std::unique_ptr fine_audio_buffer_; + Microsoft::WRL::ComPtr audio_render_client_; + uint64_t num_frames_written_ = 0; +}; + +} // namespace webrtc_win +} // namespace webrtc + +#endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_OUTPUT_WIN_H_ diff --git a/modules/audio_device/win/core_audio_utility_win.cc b/modules/audio_device/win/core_audio_utility_win.cc index cc9acd7c9e..5dc45a8a98 100644 --- a/modules/audio_device/win/core_audio_utility_win.cc +++ b/modules/audio_device/win/core_audio_utility_win.cc @@ -11,6 +11,7 @@ #include "modules/audio_device/win/core_audio_utility_win.h" #include +#include #include #include @@ -24,6 +25,7 @@ #include "rtc_base/strings/string_builder.h" #include "rtc_base/stringutils.h" +using ATL::CComHeapPtr; using Microsoft::WRL::ComPtr; using webrtc::AudioDeviceName; using webrtc::AudioParameters; @@ -44,13 +46,21 @@ bool LoadAudiosesDll() { nullptr); } +bool LoadAvrtDll() { + static const wchar_t* const kAvrtDLL = L"%WINDIR%\\system32\\Avrt.dll"; + wchar_t path[MAX_PATH] = {0}; + ExpandEnvironmentStringsW(kAvrtDLL, path, arraysize(path)); + RTC_DLOG(INFO) << rtc::ToUtf8(path); + return (LoadLibraryExW(path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH) != + nullptr); +} + ComPtr CreateDeviceEnumeratorInternal( bool allow_reinitialize) { ComPtr device_enumerator; - _com_error error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&device_enumerator)); - + _com_error error = + ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, + IID_PPV_ARGS(&device_enumerator)); if (error.Error() != S_OK) { RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error); } @@ -64,8 +74,7 @@ ComPtr CreateDeviceEnumeratorInternal( error = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (error.Error() != S_OK) { error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&device_enumerator)); + CLSCTX_ALL, IID_PPV_ARGS(&device_enumerator)); if (error.Error() != S_OK) { RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error); @@ -150,7 +159,7 @@ ComPtr CreateDeviceInternal(const std::string& device_id, std::string GetDeviceIdInternal(IMMDevice* device) { // Retrieve unique name of endpoint device. // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}". - ScopedCoMem device_id; + CComHeapPtr device_id; if (SUCCEEDED(device->GetId(&device_id))) { return rtc::ToUtf8(device_id, wcslen(device_id)); } else { @@ -181,6 +190,45 @@ std::string GetDeviceFriendlyNameInternal(IMMDevice* device) { } } +ComPtr CreateSessionManager2Internal( + IMMDevice* audio_device) { + if (!audio_device) + return ComPtr(); + + ComPtr audio_session_manager; + _com_error error = + audio_device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, + nullptr, &audio_session_manager); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioSessionManager2) failed: " + << ErrorToString(error); + } + return audio_session_manager; +} + +ComPtr CreateSessionEnumeratorInternal( + IMMDevice* audio_device) { + if (!audio_device) { + return ComPtr(); + } + + ComPtr audio_session_enumerator; + ComPtr audio_session_manager = + CreateSessionManager2Internal(audio_device); + if (!audio_session_manager.Get()) { + return audio_session_enumerator; + } + _com_error error = + audio_session_manager->GetSessionEnumerator(&audio_session_enumerator); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) + << "IAudioSessionEnumerator::IAudioSessionEnumerator failed: " + << ErrorToString(error); + return ComPtr(); + } + return audio_session_enumerator; +} + // Creates and activates an IAudioClient COM object given the selected // endpoint device. ComPtr CreateClientInternal(IMMDevice* audio_device) { @@ -188,8 +236,8 @@ ComPtr CreateClientInternal(IMMDevice* audio_device) { return ComPtr(); ComPtr audio_client; - _com_error error = audio_device->Activate( - __uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, &audio_client); + _com_error error = audio_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, + nullptr, &audio_client); if (error.Error() != S_OK) { RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient) failed: " << ErrorToString(error); @@ -202,8 +250,8 @@ ComPtr CreateClient2Internal(IMMDevice* audio_device) { return ComPtr(); ComPtr audio_client; - _com_error error = audio_device->Activate( - __uuidof(IAudioClient2), CLSCTX_INPROC_SERVER, nullptr, &audio_client); + _com_error error = audio_device->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, + nullptr, &audio_client); if (error.Error() != S_OK) { RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient2) failed: " << ErrorToString(error); @@ -346,10 +394,16 @@ HRESULT GetPreferredAudioParametersInternal(IAudioClient* client, namespace core_audio_utility { bool IsSupported() { + RTC_DLOG(INFO) << "IsSupported"; static bool g_is_supported = IsSupportedInternal(); return g_is_supported; } +bool IsMMCSSSupported() { + RTC_DLOG(INFO) << "IsMMCSSSupported"; + return LoadAvrtDll(); +} + int NumberOfActiveDevices(EDataFlow data_flow) { // Generate a collection of active audio endpoint devices for the specified // data-flow direction. @@ -468,6 +522,67 @@ bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names) { return GetDeviceNamesInternal(eRender, device_names); } +ComPtr CreateSessionManager2(IMMDevice* device) { + RTC_DLOG(INFO) << "CreateSessionManager2"; + return CreateSessionManager2Internal(device); +} + +Microsoft::WRL::ComPtr CreateSessionEnumerator( + IMMDevice* device) { + RTC_DLOG(INFO) << "CreateSessionEnumerator"; + return CreateSessionEnumeratorInternal(device); +} + +int NumberOfActiveSessions(IMMDevice* device) { + RTC_DLOG(INFO) << "NumberOfActiveSessions"; + ComPtr session_enumerator = + CreateSessionEnumerator(device); + + // Iterate over all audio sessions for the given device. + int session_count = 0; + _com_error error = session_enumerator->GetCount(&session_count); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetCount failed: " + << ErrorToString(error); + return 0; + } + RTC_DLOG(INFO) << "Total number of audio sessions: " << session_count; + + int num_active = 0; + for (int session = 0; session < session_count; session++) { + // Acquire the session control interface. + ComPtr session_control; + error = session_enumerator->GetSession(session, &session_control); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetSession failed: " + << ErrorToString(error); + return 0; + } + + // Log the display name of the audio session for debugging purposes. + CComHeapPtr display_name; + if (SUCCEEDED(session_control->GetDisplayName(&display_name))) { + RTC_DLOG(INFO) << "display name: " + << rtc::ToUtf8(display_name, wcslen(display_name)); + } + + // Get the current state and check if the state is active or not. + AudioSessionState state; + error = session_control->GetState(&state); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "IAudioSessionControl::GetState failed: " + << ErrorToString(error); + return 0; + } + if (state == AudioSessionStateActive) { + ++num_active; + } + } + + RTC_DLOG(INFO) << "Number of active audio sessions: " << num_active; + return num_active; +} + ComPtr CreateClient(const std::string& device_id, EDataFlow data_flow, ERole role) { @@ -624,11 +739,12 @@ HRESULT SharedModeInitialize(IAudioClient* client, uint32_t* endpoint_buffer_size) { RTC_DLOG(INFO) << "SharedModeInitialize"; RTC_DCHECK(client); - // Use default flags (i.e, don't set AUDCLNT_STREAMFLAGS_NOPERSIST) to - // ensure that the volume level and muting state for a rendering session - // are persistent across system restarts. The volume level and muting - // state for a capture session are never persistent. - DWORD stream_flags = 0; + // The AUDCLNT_STREAMFLAGS_NOPERSIST flag disables persistence of the volume + // and mute settings for a session that contains rendering streams. + // By default, the volume level and muting state for a rendering session are + // persistent across system restarts. The volume level and muting state for a + // capture session are never persistent. + DWORD stream_flags = AUDCLNT_STREAMFLAGS_NOPERSIST; // Enable event-driven streaming if a valid event handle is provided. // After the stream starts, the audio engine will signal the event handle @@ -739,6 +855,22 @@ ComPtr CreateAudioClock(IAudioClient* client) { return audio_clock; } +ComPtr CreateSimpleAudioVolume(IAudioClient* client) { + RTC_DLOG(INFO) << "CreateSimpleAudioVolume"; + RTC_DCHECK(client); + // Get access to the ISimpleAudioVolume interface. This interface enables a + // client to control the master volume level of an audio session. + ComPtr simple_audio_volume; + _com_error error = client->GetService(IID_PPV_ARGS(&simple_audio_volume)); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) + << "IAudioClient::GetService(IID_ISimpleAudioVolume) failed: " + << ErrorToString(error); + return ComPtr(); + } + return simple_audio_volume; +} + bool FillRenderEndpointBufferWithSilence(IAudioClient* client, IAudioRenderClient* render_client) { RTC_DLOG(INFO) << "FillRenderEndpointBufferWithSilence"; diff --git a/modules/audio_device/win/core_audio_utility_win.h b/modules/audio_device/win/core_audio_utility_win.h index c78743191b..87e49606ca 100644 --- a/modules/audio_device/win/core_audio_utility_win.h +++ b/modules/audio_device/win/core_audio_utility_win.h @@ -12,6 +12,7 @@ #define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_UTILITY_WIN_H_ #include +#include #include #include #include @@ -26,6 +27,8 @@ #include "modules/audio_device/include/audio_device_defines.h" #include "rtc_base/logging.h" +#pragma comment(lib, "Avrt.lib") + namespace webrtc { namespace webrtc_win { @@ -78,6 +81,10 @@ class ScopedMMCSSRegistration { class ScopedCOMInitializer { public: // Enum value provided to initialize the thread as an MTA instead of STA. + // There are two types of apartments, Single Threaded Apartments (STAs) + // and Multi Threaded Apartments (MTAs). Within a given process there can + // be multiple STA’s but there is only one MTA. STA is typically used by + // "GUI applications" and MTA by "worker threads" with no UI message loop. enum SelectMTA { kMTA }; // Constructor for STA initialization. @@ -107,9 +114,23 @@ class ScopedCOMInitializer { void Initialize(COINIT init) { // Initializes the COM library for use by the calling thread, sets the // thread's concurrency model, and creates a new apartment for the thread - // if one is required. + // if one is required. CoInitializeEx must be called at least once, and is + // usually called only once, for each thread that uses the COM library. hr_ = CoInitializeEx(NULL, init); - RTC_CHECK_NE(RPC_E_CHANGED_MODE, hr_) << "Invalid COM thread model change"; + RTC_CHECK_NE(RPC_E_CHANGED_MODE, hr_) + << "Invalid COM thread model change (MTA->STA)"; + // Multiple calls to CoInitializeEx by the same thread are allowed as long + // as they pass the same concurrency flag, but subsequent valid calls + // return S_FALSE. To close the COM library gracefully on a thread, each + // successful call to CoInitializeEx, including any call that returns + // S_FALSE, must be balanced by a corresponding call to CoUninitialize. + if (hr_ == S_OK) { + RTC_DLOG(INFO) + << "The COM library was initialized successfully on this thread"; + } else if (hr_ == S_FALSE) { + RTC_DLOG(WARNING) + << "The COM library is already initialized on this thread"; + } } HRESULT hr_; }; @@ -263,6 +284,12 @@ namespace core_audio_utility { // other methods in this class. bool IsSupported(); +// Returns true if Multimedia Class Scheduler service (MMCSS) is supported. +// The MMCSS enables multimedia applications to ensure that their time-sensitive +// processing receives prioritized access to CPU resources without denying CPU +// resources to lower-priority applications. +bool IsMMCSSSupported(); + // The MMDevice API lets clients discover the audio endpoint devices in the // system and determine which devices are suitable for the application to use. // Header file Mmdeviceapi.h defines the interfaces in the MMDevice API. @@ -320,7 +347,22 @@ bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names); // device. Header files Audioclient.h and Audiopolicy.h define the WASAPI // interfaces. -// Create an IAudioClient instance for a specific device or the default +// Creates an IAudioSessionManager2 interface for the specified |device|. +// This interface provides access to e.g. the IAudioSessionEnumerator +Microsoft::WRL::ComPtr CreateSessionManager2( + IMMDevice* device); + +// Creates an IAudioSessionEnumerator interface for the specified |device|. +// The client can use the interface to enumerate audio sessions on the audio +// device +Microsoft::WRL::ComPtr CreateSessionEnumerator( + IMMDevice* device); + +// Number of active audio sessions for the given |device|. Expired or inactive +// sessions are not included. +int NumberOfActiveSessions(IMMDevice* device); + +// Creates an IAudioClient instance for a specific device or the default // device specified by data-flow direction and role. Microsoft::WRL::ComPtr CreateClient(const std::string& device_id, EDataFlow data_flow, @@ -384,8 +426,7 @@ HRESULT GetPreferredAudioParameters(IAudioClient* client, // TODO(henrika): // - use IAudioClient2::SetClientProperties before calling this method // - IAudioClient::Initialize(your_format, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM -// | -// AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY) +// AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY) HRESULT SharedModeInitialize(IAudioClient* client, const WAVEFORMATEXTENSIBLE* format, HANDLE event_handle, @@ -410,12 +451,17 @@ Microsoft::WRL::ComPtr CreateCaptureClient( // data rate and the current position in the stream. Microsoft::WRL::ComPtr CreateAudioClock(IAudioClient* client); +// Creates an ISimpleAudioVolume interface for an existing IAudioClient given by +// |client|. This interface enables a client to control the master volume level +// of an active audio session. +Microsoft::WRL::ComPtr CreateSimpleAudioVolume( + IAudioClient* client); + // Fills up the endpoint rendering buffer with silence for an existing // IAudioClient given by |client| and a corresponding IAudioRenderClient // given by |render_client|. bool FillRenderEndpointBufferWithSilence(IAudioClient* client, IAudioRenderClient* render_client); - // Transforms a WAVEFORMATEXTENSIBLE struct to a human-readable string. std::string WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format); diff --git a/modules/audio_device/win/core_audio_utility_win_unittest.cc b/modules/audio_device/win/core_audio_utility_win_unittest.cc index 35241762eb..3944cb002d 100644 --- a/modules/audio_device/win/core_audio_utility_win_unittest.cc +++ b/modules/audio_device/win/core_audio_utility_win_unittest.cc @@ -11,8 +11,11 @@ #include "modules/audio_device/win/core_audio_utility_win.h" #include "rtc_base/arraysize.h" #include "rtc_base/logging.h" +#include "rtc_base/win/windows_version.h" #include "test/gtest.h" +#include "system_wrappers/include/sleep.h" + using Microsoft::WRL::ComPtr; using webrtc::AudioDeviceName; @@ -53,7 +56,8 @@ bool ShouldAbortTest(bool requirements_satisfied, // CoreAudioUtilityWinTest test fixture. class CoreAudioUtilityWinTest : public ::testing::Test { protected: - CoreAudioUtilityWinTest() { + CoreAudioUtilityWinTest() + : com_init_(webrtc_win::ScopedCOMInitializer::kMTA) { // We must initialize the COM library on a thread before we calling any of // the library functions. All COM functions will return CO_E_NOTINITIALIZED // otherwise. @@ -242,31 +246,107 @@ TEST_F(CoreAudioUtilityWinTest, GetOutputDeviceNames) { 2 + core_audio_utility::NumberOfActiveDevices(eRender)); } +TEST_F(CoreAudioUtilityWinTest, CreateSessionManager2) { + ABORT_TEST_IF_NOT(DevicesAvailable() && + rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN7); + + EDataFlow data_flow[] = {eRender, eCapture}; + + // Obtain reference to an IAudioSessionManager2 interface for a default audio + // endpoint device specified by two different data flows and the |eConsole| + // role. + for (size_t i = 0; i < arraysize(data_flow); ++i) { + ComPtr device(core_audio_utility::CreateDevice( + AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole)); + EXPECT_TRUE(device.Get()); + ComPtr session_manager = + core_audio_utility::CreateSessionManager2(device.Get()); + EXPECT_TRUE(session_manager.Get()); + } +} + +TEST_F(CoreAudioUtilityWinTest, CreateSessionEnumerator) { + ABORT_TEST_IF_NOT(DevicesAvailable() && + rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN7); + + EDataFlow data_flow[] = {eRender, eCapture}; + + // Obtain reference to an IAudioSessionEnumerator interface for a default + // audio endpoint device specified by two different data flows and the + // |eConsole| role. + for (size_t i = 0; i < arraysize(data_flow); ++i) { + ComPtr device(core_audio_utility::CreateDevice( + AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole)); + EXPECT_TRUE(device.Get()); + ComPtr session_enumerator = + core_audio_utility::CreateSessionEnumerator(device.Get()); + EXPECT_TRUE(session_enumerator.Get()); + + // Perform a sanity test of the interface by asking for the total number + // of audio sessions that are open on the audio device. Note that, we do + // not check if the session is active or not. + int session_count = 0; + EXPECT_TRUE(SUCCEEDED(session_enumerator->GetCount(&session_count))); + EXPECT_GE(session_count, 0); + } +} + +TEST_F(CoreAudioUtilityWinTest, NumberOfActiveSessions) { + ABORT_TEST_IF_NOT(DevicesAvailable() && + rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN7); + + EDataFlow data_flow[] = {eRender, eCapture}; + + // Count number of active audio session for a default audio endpoint device + // specified by two different data flows and the |eConsole| role. + // Ensure that the number of active audio sessions is less than or equal to + // the total number of audio sessions on that same device. + for (size_t i = 0; i < arraysize(data_flow); ++i) { + // Create an audio endpoint device. + ComPtr device(core_audio_utility::CreateDevice( + AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole)); + EXPECT_TRUE(device.Get()); + + // Ask for total number of audio sessions on the created device. + ComPtr session_enumerator = + core_audio_utility::CreateSessionEnumerator(device.Get()); + EXPECT_TRUE(session_enumerator.Get()); + int total_session_count = 0; + EXPECT_TRUE(SUCCEEDED(session_enumerator->GetCount(&total_session_count))); + EXPECT_GE(total_session_count, 0); + + // Use NumberOfActiveSessions and get number of active audio sessions. + int active_session_count = + core_audio_utility::NumberOfActiveSessions(device.Get()); + EXPECT_LE(active_session_count, total_session_count); + } +} + TEST_F(CoreAudioUtilityWinTest, CreateClient) { ABORT_TEST_IF_NOT(DevicesAvailable()); - EDataFlow data[] = {eRender, eCapture}; + EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioClient interface for a default audio endpoint // device specified by two different data flows and the |eConsole| role. - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client = core_audio_utility::CreateClient( - AudioDeviceName::kDefaultDeviceId, data[i], eConsole); + AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); } } -// TODO(henrik): enable when support for Windows version querying is added. -TEST_F(CoreAudioUtilityWinTest, DISABLED_CreateClient2) { - ABORT_TEST_IF_NOT(DevicesAvailable()); +TEST_F(CoreAudioUtilityWinTest, CreateClient2) { + ABORT_TEST_IF_NOT(DevicesAvailable() && + rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN10); - EDataFlow data[] = {eRender, eCapture}; + EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioClient2 interface for a default audio endpoint // device specified by two different data flows and the |eConsole| role. - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client = core_audio_utility::CreateClient2( - AudioDeviceName::kDefaultDeviceId, data[i], eConsole); + AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); } } @@ -325,16 +405,16 @@ TEST_F(CoreAudioUtilityWinTest, IsFormatSupported) { TEST_F(CoreAudioUtilityWinTest, GetDevicePeriod) { ABORT_TEST_IF_NOT(DevicesAvailable()); - EDataFlow data[] = {eRender, eCapture}; + EDataFlow data_flow[] = {eRender, eCapture}; // Verify that the device periods are valid for the default render and // capture devices. - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; REFERENCE_TIME shared_time_period = 0; REFERENCE_TIME exclusive_time_period = 0; client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, - data[i], eConsole); + data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetDevicePeriod( client.Get(), AUDCLNT_SHAREMODE_SHARED, &shared_time_period))); @@ -349,25 +429,25 @@ TEST_F(CoreAudioUtilityWinTest, GetDevicePeriod) { TEST_F(CoreAudioUtilityWinTest, GetPreferredAudioParameters) { ABORT_TEST_IF_NOT(DevicesAvailable()); - EDataFlow data[] = {eRender, eCapture}; + EDataFlow data_flow[] = {eRender, eCapture}; // Verify that the preferred audio parameters are OK for the default render // and capture devices. - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { webrtc::AudioParameters params; EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetPreferredAudioParameters( - AudioDeviceName::kDefaultDeviceId, data[i] == eRender, ¶ms))); + AudioDeviceName::kDefaultDeviceId, data_flow[i] == eRender, ¶ms))); EXPECT_TRUE(params.is_valid()); EXPECT_TRUE(params.is_complete()); } // Verify that the preferred audio parameters are OK for the default // communication devices. - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { webrtc::AudioParameters params; EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetPreferredAudioParameters( - AudioDeviceName::kDefaultCommunicationsDeviceId, data[i] == eRender, - ¶ms))); + AudioDeviceName::kDefaultCommunicationsDeviceId, + data_flow[i] == eRender, ¶ms))); EXPECT_TRUE(params.is_valid()); EXPECT_TRUE(params.is_complete()); } @@ -443,23 +523,23 @@ TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) { TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) { ABORT_TEST_IF_NOT(DevicesAvailable()); - EDataFlow data[] = {eRender, eCapture}; + EDataFlow data_flow[] = {eRender, eCapture}; WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; ComPtr render_client; ComPtr capture_client; // Create a default client for the given data-flow direction. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, - data[i], eConsole); + data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); - if (data[i] == eRender) { + if (data_flow[i] == eRender) { // It is not possible to create a render client using an unitialized // client interface. render_client = core_audio_utility::CreateRenderClient(client.Get()); @@ -471,7 +551,7 @@ TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) { render_client = core_audio_utility::CreateRenderClient(client.Get()); EXPECT_TRUE(render_client.Get()); EXPECT_GT(endpoint_buffer_size, 0u); - } else if (data[i] == eCapture) { + } else if (data_flow[i] == eCapture) { // It is not possible to create a capture client using an unitialized // client interface. capture_client = core_audio_utility::CreateCaptureClient(client.Get()); @@ -490,18 +570,18 @@ TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) { TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) { ABORT_TEST_IF_NOT(DevicesAvailable()); - EDataFlow data[] = {eRender, eCapture}; + EDataFlow data_flow[] = {eRender, eCapture}; WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; - for (size_t i = 0; i < arraysize(data); ++i) { + for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; ComPtr audio_clock; // Create a default client for the given data-flow direction. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, - data[i], eConsole); + data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); @@ -525,6 +605,55 @@ TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) { } } +TEST_F(CoreAudioUtilityWinTest, CreateSimpleAudioVolume) { + ABORT_TEST_IF_NOT(DevicesAvailable()); + + EDataFlow data_flow[] = {eRender, eCapture}; + + WAVEFORMATPCMEX format; + uint32_t endpoint_buffer_size = 0; + + for (size_t i = 0; i < arraysize(data_flow); ++i) { + ComPtr client; + ComPtr simple_audio_volume; + + // Create a default client for the given data-flow direction. + client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, + data_flow[i], eConsole); + EXPECT_TRUE(client.Get()); + EXPECT_TRUE(SUCCEEDED( + core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); + + // It is not possible to create an audio volume using an uninitialized + // client interface. + simple_audio_volume = + core_audio_utility::CreateSimpleAudioVolume(client.Get()); + EXPECT_FALSE(simple_audio_volume.Get()); + + // Do a proper initialization and verify that it works this time. + core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, + &endpoint_buffer_size); + simple_audio_volume = + core_audio_utility::CreateSimpleAudioVolume(client.Get()); + EXPECT_TRUE(simple_audio_volume.Get()); + EXPECT_GT(endpoint_buffer_size, 0u); + + // Use the audio volume interface and validate that it works. The volume + // level should be value in the range 0.0 to 1.0 at first call. + float volume = 0.0; + EXPECT_TRUE(SUCCEEDED(simple_audio_volume->GetMasterVolume(&volume))); + EXPECT_GE(volume, 0.0); + EXPECT_LE(volume, 1.0); + + // Next, set a new volume and verify that the setter does its job. + const float target_volume = 0.5; + EXPECT_TRUE(SUCCEEDED( + simple_audio_volume->SetMasterVolume(target_volume, nullptr))); + EXPECT_TRUE(SUCCEEDED(simple_audio_volume->GetMasterVolume(&volume))); + EXPECT_EQ(volume, target_volume); + } +} + TEST_F(CoreAudioUtilityWinTest, FillRenderEndpointBufferWithSilence) { ABORT_TEST_IF_NOT(DevicesAvailable()); diff --git a/sdk/android/src/jni/audio_device/audio_device_module.cc b/sdk/android/src/jni/audio_device/audio_device_module.cc index dd08a86405..0b18fe4b91 100644 --- a/sdk/android/src/jni/audio_device/audio_device_module.cc +++ b/sdk/android/src/jni/audio_device/audio_device_module.cc @@ -31,8 +31,12 @@ namespace { // implementations. // // An instance can be created on any thread, but must then be used on one and -// the same thread. All public methods must also be called on the same thread. A -// thread checker will RTC_DCHECK if any method is called on an invalid thread. +// the same thread. All public methods must also be called on the same thread. +// A thread checker will RTC_DCHECK if any method is called on an invalid +// thread. +// TODO(henrika): it might be useful to also support a scenario where the ADM +// is constructed on thread T1, used on thread T2 and destructed on T2 or T3. +// If so, care must be taken to ensure that only T2 is a COM thread. class AndroidAudioDeviceModule : public AudioDeviceModule { public: // For use with UMA logging. Must be kept in sync with histograms.xml in