Adds support for new Windows ADM with limited API support.
Summary of what this CL does: Existing users can keep using the old ADM for Windows as before. A new ADM for Windows is created and a dedicated factory method is used to create it. The old way (using AudioDeviceImpl) is not utilized. The new ADM is based on a structure where most of the "action" takes place in new AudioInput/AudioOutput implementations. This is inline with our mobile platforms and also makes it easier to break out common parts into a base class. The AudioDevice unittest has always mainly focused on the "Start/Stop"- parts of the ADM and not the complete ADM interface. This new ADM supports all tests in AudioDeviceTest and is therefore tested in combination with the old version. A value-parametrized test us added for Windows builds. Improved readability, threading model and makes the code easier to maintain. Uses the previously landed methods in webrtc::webrtc_win::core_audio_utility. Bug: webrtc:9265 Change-Id: If2894b44528e74a181cf7ad1216f57386ee3a24d Reviewed-on: https://webrtc-review.googlesource.com/78060 Reviewed-by: Oskar Sundbom <ossu@webrtc.org> Commit-Queue: Henrik Andreassson <henrika@webrtc.org> Cr-Commit-Position: refs/heads/master@{#23554}
This commit is contained in:
@ -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,
|
# Contains default implementations of webrtc::AudioDeviceModule for Windows,
|
||||||
# Linux, Mac, iOS and Android.
|
# Linux, Mac, iOS and Android.
|
||||||
rtc_source_set("audio_device_impl") {
|
rtc_source_set("audio_device_impl") {
|
||||||
@ -187,9 +238,6 @@ rtc_source_set("audio_device_impl") {
|
|||||||
if (rtc_include_internal_audio_device && is_ios) {
|
if (rtc_include_internal_audio_device && is_ios) {
|
||||||
deps += [ ":audio_device_ios_objc" ]
|
deps += [ ":audio_device_ios_objc" ]
|
||||||
}
|
}
|
||||||
if (is_win) {
|
|
||||||
deps += [ ":audio_device_name" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
sources = [
|
sources = [
|
||||||
"dummy/audio_device_dummy.cc",
|
"dummy/audio_device_dummy.cc",
|
||||||
@ -339,8 +387,6 @@ rtc_source_set("audio_device_impl") {
|
|||||||
sources += [
|
sources += [
|
||||||
"win/audio_device_core_win.cc",
|
"win/audio_device_core_win.cc",
|
||||||
"win/audio_device_core_win.h",
|
"win/audio_device_core_win.h",
|
||||||
"win/core_audio_utility_win.cc",
|
|
||||||
"win/core_audio_utility_win.h",
|
|
||||||
]
|
]
|
||||||
libs = [
|
libs = [
|
||||||
# Required for the built-in WASAPI AEC.
|
# Required for the built-in WASAPI AEC.
|
||||||
@ -449,8 +495,12 @@ if (rtc_include_tests) {
|
|||||||
if (is_linux || is_mac || is_win) {
|
if (is_linux || is_mac || is_win) {
|
||||||
sources += [ "audio_device_unittest.cc" ]
|
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" ]
|
sources += [ "win/core_audio_utility_win_unittest.cc" ]
|
||||||
|
deps += [
|
||||||
|
":audio_device_module_from_input_and_output",
|
||||||
|
":windows_core_audio_utility",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
if (is_android) {
|
if (is_android) {
|
||||||
# Need to disable error due to the line in
|
# Need to disable error due to the line in
|
||||||
|
|||||||
@ -72,6 +72,15 @@ namespace webrtc {
|
|||||||
rtc::scoped_refptr<AudioDeviceModule> AudioDeviceModule::Create(
|
rtc::scoped_refptr<AudioDeviceModule> AudioDeviceModule::Create(
|
||||||
const AudioLayer audio_layer) {
|
const AudioLayer audio_layer) {
|
||||||
RTC_LOG(INFO) << __FUNCTION__;
|
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.
|
// Create the generic reference counted (platform independent) implementation.
|
||||||
rtc::scoped_refptr<AudioDeviceModuleImpl> audioDevice(
|
rtc::scoped_refptr<AudioDeviceModuleImpl> audioDevice(
|
||||||
new rtc::RefCountedObject<AudioDeviceModuleImpl>(audio_layer));
|
new rtc::RefCountedObject<AudioDeviceModuleImpl>(audio_layer));
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
#include "api/array_view.h"
|
#include "api/array_view.h"
|
||||||
@ -29,6 +30,10 @@
|
|||||||
#include "rtc_base/timeutils.h"
|
#include "rtc_base/timeutils.h"
|
||||||
#include "test/gmock.h"
|
#include "test/gmock.h"
|
||||||
#include "test/gtest.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::_;
|
||||||
using ::testing::AtLeast;
|
using ::testing::AtLeast;
|
||||||
@ -445,17 +450,17 @@ class MockAudioTransport : public test::MockAudioTransport {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// AudioDeviceTest test fixture.
|
// AudioDeviceTest test fixture.
|
||||||
class AudioDeviceTest : public ::testing::Test {
|
class AudioDeviceTest
|
||||||
|
: public ::testing::TestWithParam<webrtc::AudioDeviceModule::AudioLayer> {
|
||||||
protected:
|
protected:
|
||||||
AudioDeviceTest() : event_(false, false) {
|
AudioDeviceTest() : audio_layer_(GetParam()), event_(false, false) {
|
||||||
#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
|
#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
|
||||||
!defined(WEBRTC_DUMMY_AUDIO_BUILD)
|
!defined(WEBRTC_DUMMY_AUDIO_BUILD)
|
||||||
rtc::LogMessage::LogToDebug(rtc::LS_INFO);
|
rtc::LogMessage::LogToDebug(rtc::LS_INFO);
|
||||||
// Add extra logging fields here if needed for debugging.
|
// Add extra logging fields here if needed for debugging.
|
||||||
// rtc::LogMessage::LogTimestamps();
|
rtc::LogMessage::LogTimestamps();
|
||||||
// rtc::LogMessage::LogThreads();
|
rtc::LogMessage::LogThreads();
|
||||||
audio_device_ =
|
audio_device_ = CreateAudioDevice();
|
||||||
AudioDeviceModule::Create(AudioDeviceModule::kPlatformDefaultAudio);
|
|
||||||
EXPECT_NE(audio_device_.get(), nullptr);
|
EXPECT_NE(audio_device_.get(), nullptr);
|
||||||
AudioDeviceModule::AudioLayer audio_layer;
|
AudioDeviceModule::AudioLayer audio_layer;
|
||||||
int got_platform_audio_layer =
|
int got_platform_audio_layer =
|
||||||
@ -506,6 +511,32 @@ class AudioDeviceTest : public ::testing::Test {
|
|||||||
return audio_device_;
|
return audio_device_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rtc::scoped_refptr<AudioDeviceModule> 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>(
|
||||||
|
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() {
|
void StartPlayout() {
|
||||||
EXPECT_FALSE(audio_device()->Playing());
|
EXPECT_FALSE(audio_device()->Playing());
|
||||||
EXPECT_EQ(0, audio_device()->InitPlayout());
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
||||||
@ -534,17 +565,70 @@ class AudioDeviceTest : public ::testing::Test {
|
|||||||
EXPECT_FALSE(audio_device()->RecordingIsInitialized());
|
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:
|
private:
|
||||||
|
#ifdef WEBRTC_WIN
|
||||||
|
// Windows Core Audio based ADM needs to run on a COM initialized thread.
|
||||||
|
std::unique_ptr<webrtc_win::ScopedCOMInitializer> com_initializer_;
|
||||||
|
#endif
|
||||||
|
AudioDeviceModule::AudioLayer audio_layer_;
|
||||||
bool requirements_satisfied_ = true;
|
bool requirements_satisfied_ = true;
|
||||||
rtc::Event event_;
|
rtc::Event event_;
|
||||||
rtc::scoped_refptr<AudioDeviceModule> audio_device_;
|
rtc::scoped_refptr<AudioDeviceModule> audio_device_;
|
||||||
bool stereo_playout_ = false;
|
bool stereo_playout_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Uses the test fixture to create, initialize and destruct the ADM.
|
// Instead of using the test fixture, verify that the different factory methods
|
||||||
TEST_F(AudioDeviceTest, ConstructDestruct) {}
|
// work as intended.
|
||||||
|
TEST(AudioDeviceTestWin, ConstructDestructWithFactory) {
|
||||||
|
rtc::scoped_refptr<AudioDeviceModule> 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());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
// Initialization is part of the test fixture.
|
// Initialization is part of the test fixture.
|
||||||
EXPECT_TRUE(audio_device()->Initialized());
|
EXPECT_TRUE(audio_device()->Initialized());
|
||||||
@ -552,28 +636,105 @@ TEST_F(AudioDeviceTest, InitTerminate) {
|
|||||||
EXPECT_FALSE(audio_device()->Initialized());
|
EXPECT_FALSE(audio_device()->Initialized());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests Start/Stop playout without any registered audio callback.
|
// Enumerate all available and active output devices.
|
||||||
TEST_F(AudioDeviceTest, StartStopPlayout) {
|
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());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
StartPlayout();
|
|
||||||
StopPlayout();
|
|
||||||
StartPlayout();
|
StartPlayout();
|
||||||
StopPlayout();
|
StopPlayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests Start/Stop recording without any registered audio callback.
|
// Tests Start/Stop recording without any registered audio callback.
|
||||||
TEST_F(AudioDeviceTest, StartStopRecording) {
|
TEST_P(AudioDeviceTest, StartStopRecording) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
StartRecording();
|
StartRecording();
|
||||||
StopRecording();
|
StopRecording();
|
||||||
StartRecording();
|
|
||||||
StopRecording();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests Init/Stop/Init recording without any registered audio callback.
|
// Tests Init/Stop/Init recording without any registered audio callback.
|
||||||
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=8041 for details
|
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=8041 for details
|
||||||
// on why this test is useful.
|
// on why this test is useful.
|
||||||
TEST_F(AudioDeviceTest, InitStopInitRecording) {
|
TEST_P(AudioDeviceTest, InitStopInitRecording) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
EXPECT_EQ(0, audio_device()->InitRecording());
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
||||||
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
|
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
|
||||||
@ -583,7 +744,7 @@ TEST_F(AudioDeviceTest, InitStopInitRecording) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tests Init/Stop/Init recording while playout is active.
|
// Tests Init/Stop/Init recording while playout is active.
|
||||||
TEST_F(AudioDeviceTest, InitStopInitRecordingWhilePlaying) {
|
TEST_P(AudioDeviceTest, InitStopInitRecordingWhilePlaying) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
StartPlayout();
|
StartPlayout();
|
||||||
EXPECT_EQ(0, audio_device()->InitRecording());
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
||||||
@ -595,7 +756,7 @@ TEST_F(AudioDeviceTest, InitStopInitRecordingWhilePlaying) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tests Init/Stop/Init playout without any registered audio callback.
|
// Tests Init/Stop/Init playout without any registered audio callback.
|
||||||
TEST_F(AudioDeviceTest, InitStopInitPlayout) {
|
TEST_P(AudioDeviceTest, InitStopInitPlayout) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
EXPECT_EQ(0, audio_device()->InitPlayout());
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
||||||
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
|
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
|
||||||
@ -605,7 +766,7 @@ TEST_F(AudioDeviceTest, InitStopInitPlayout) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tests Init/Stop/Init playout while recording is active.
|
// Tests Init/Stop/Init playout while recording is active.
|
||||||
TEST_F(AudioDeviceTest, InitStopInitPlayoutWhileRecording) {
|
TEST_P(AudioDeviceTest, InitStopInitPlayoutWhileRecording) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
StartRecording();
|
StartRecording();
|
||||||
EXPECT_EQ(0, audio_device()->InitPlayout());
|
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
|
// 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
|
// since parameter are not provided in the each callback. We therefore test and
|
||||||
// verify the parameters in the fake audio transport implementation instead.
|
// verify the parameters in the fake audio transport implementation instead.
|
||||||
TEST_F(AudioDeviceTest, StartPlayoutVerifyCallbacks) {
|
TEST_P(AudioDeviceTest, StartPlayoutVerifyCallbacks) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
MockAudioTransport mock(TransportType::kPlay);
|
MockAudioTransport mock(TransportType::kPlay);
|
||||||
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
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
|
// Start recording and verify that the native audio layer starts providing real
|
||||||
// audio samples using the RecordedDataIsAvailable() callback.
|
// audio samples using the RecordedDataIsAvailable() callback.
|
||||||
TEST_F(AudioDeviceTest, StartRecordingVerifyCallbacks) {
|
TEST_P(AudioDeviceTest, StartRecordingVerifyCallbacks) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
MockAudioTransport mock(TransportType::kRecord);
|
MockAudioTransport mock(TransportType::kRecord);
|
||||||
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
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
|
// Start playout and recording (full-duplex audio) and verify that audio is
|
||||||
// active in both directions.
|
// active in both directions.
|
||||||
TEST_F(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) {
|
TEST_P(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
MockAudioTransport mock(TransportType::kPlayAndRecord);
|
MockAudioTransport mock(TransportType::kPlayAndRecord);
|
||||||
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
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
|
// 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
|
// (max and average) of the FIFO. The size of the FIFO is increased by the
|
||||||
// recording side and decreased by the playout side.
|
// recording side and decreased by the playout side.
|
||||||
TEST_F(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) {
|
TEST_P(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) {
|
||||||
SKIP_TEST_IF_NOT(requirements_satisfied());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
||||||
FifoAudioStream audio_stream;
|
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
|
// 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
|
// close to the speaker to ensure highest possible echo. It is also recommended
|
||||||
// to run the test at highest possible output volume.
|
// 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());
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
||||||
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
||||||
LatencyAudioStream audio_stream;
|
LatencyAudioStream audio_stream;
|
||||||
@ -739,4 +900,20 @@ TEST_F(AudioDeviceTest, DISABLED_MeasureLoopbackLatency) {
|
|||||||
audio_stream.PrintResults();
|
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
|
} // namespace webrtc
|
||||||
|
|||||||
@ -65,7 +65,9 @@ void FineAudioBuffer::GetPlayoutData(rtc::ArrayView<int16_t> audio_buffer,
|
|||||||
while (playout_buffer_.size() < audio_buffer.size()) {
|
while (playout_buffer_.size() < audio_buffer.size()) {
|
||||||
// Get 10ms decoded audio from WebRTC. The ADB knows about number of
|
// Get 10ms decoded audio from WebRTC. The ADB knows about number of
|
||||||
// channels; hence we can ask for number of samples per channel here.
|
// channels; hence we can ask for number of samples per channel here.
|
||||||
audio_device_buffer_->RequestPlayoutData(playout_samples_per_channel_10ms_);
|
if (audio_device_buffer_->RequestPlayoutData(
|
||||||
|
playout_samples_per_channel_10ms_) ==
|
||||||
|
static_cast<int32_t>(playout_samples_per_channel_10ms_)) {
|
||||||
// Append 10ms to the end of the local buffer taking number of channels
|
// Append 10ms to the end of the local buffer taking number of channels
|
||||||
// into account.
|
// into account.
|
||||||
const size_t num_elements_10ms =
|
const size_t num_elements_10ms =
|
||||||
@ -77,6 +79,13 @@ void FineAudioBuffer::GetPlayoutData(rtc::ArrayView<int16_t> audio_buffer,
|
|||||||
return playout_channels_ * samples_per_channel_10ms;
|
return playout_channels_ * samples_per_channel_10ms;
|
||||||
});
|
});
|
||||||
RTC_DCHECK_EQ(num_elements_10ms, written_elements);
|
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.
|
// Provide the requested number of bytes to the consumer.
|
||||||
|
|||||||
@ -28,15 +28,16 @@ class AudioDeviceModule : public rtc::RefCountInterface {
|
|||||||
|
|
||||||
enum AudioLayer {
|
enum AudioLayer {
|
||||||
kPlatformDefaultAudio = 0,
|
kPlatformDefaultAudio = 0,
|
||||||
kWindowsCoreAudio = 2,
|
kWindowsCoreAudio,
|
||||||
kLinuxAlsaAudio = 3,
|
kWindowsCoreAudio2, // experimental
|
||||||
kLinuxPulseAudio = 4,
|
kLinuxAlsaAudio,
|
||||||
kAndroidJavaAudio = 5,
|
kLinuxPulseAudio,
|
||||||
kAndroidOpenSLESAudio = 6,
|
kAndroidJavaAudio,
|
||||||
kAndroidJavaInputAndOpenSLESOutputAudio = 7,
|
kAndroidOpenSLESAudio,
|
||||||
kAndroidAAudioAudio = 8,
|
kAndroidJavaInputAndOpenSLESOutputAudio,
|
||||||
kAndroidJavaInputAndAAudioOutputAudio = 9,
|
kAndroidAAudioAudio,
|
||||||
kDummyAudio = 10
|
kAndroidJavaInputAndAAudioOutputAudio,
|
||||||
|
kDummyAudio,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WindowsDeviceType {
|
enum WindowsDeviceType {
|
||||||
|
|||||||
40
modules/audio_device/include/audio_device_factory.cc
Normal file
40
modules/audio_device/include/audio_device_factory.cc
Normal file
@ -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<AudioDeviceModule>
|
||||||
|
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<webrtc_win::CoreAudioInput>(),
|
||||||
|
rtc::MakeUnique<webrtc_win::CoreAudioOutput>());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
40
modules/audio_device/include/audio_device_factory.h
Normal file
40
modules/audio_device/include/audio_device_factory.h
Normal file
@ -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<AudioDeviceModule> CreateAudioDevice() {
|
||||||
|
// // Tell COM that this thread shall live in the MTA.
|
||||||
|
// com_initializer_ = rtc::MakeUnique<webrtc_win::ScopedCOMInitializer>(
|
||||||
|
// webrtc_win::ScopedCOMInitializer::kMTA);
|
||||||
|
// if (!com_initializer_->Succeeded()) {
|
||||||
|
// return nullptr;
|
||||||
|
// }
|
||||||
|
// return CreateWindowsCoreAudioAudioDeviceModule();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private:
|
||||||
|
// std::unique_ptr<webrtc_win::ScopedCOMInitializer> com_initializer_;
|
||||||
|
//
|
||||||
|
rtc::scoped_refptr<AudioDeviceModule> CreateWindowsCoreAudioAudioDeviceModule();
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_FACTORY_H_
|
||||||
423
modules/audio_device/win/audio_device_module_win.cc
Normal file
423
modules/audio_device/win/audio_device_module_win.cc
Normal file
@ -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 <utility>
|
||||||
|
|
||||||
|
#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<AudioInput> audio_input,
|
||||||
|
std::unique_ptr<AudioOutput> 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<AudioDeviceBuffer>();
|
||||||
|
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<AudioInput> input_;
|
||||||
|
|
||||||
|
// Implements the AudioOutput interface and deals with audio rendering parts.
|
||||||
|
const std::unique_ptr<AudioOutput> 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<AudioDeviceBuffer> audio_device_buffer_;
|
||||||
|
|
||||||
|
// Set to true after a successful call to Init(). Cleared by Terminate().
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
rtc::scoped_refptr<AudioDeviceModule>
|
||||||
|
CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput(
|
||||||
|
std::unique_ptr<AudioInput> audio_input,
|
||||||
|
std::unique_ptr<AudioOutput> audio_output) {
|
||||||
|
RTC_LOG(INFO) << __FUNCTION__;
|
||||||
|
return new rtc::RefCountedObject<WindowsAudioDeviceModule>(
|
||||||
|
std::move(audio_input), std::move(audio_output));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc_win
|
||||||
|
} // namespace webrtc
|
||||||
79
modules/audio_device/win/audio_device_module_win.h
Normal file
79
modules/audio_device/win/audio_device_module_win.h
Normal file
@ -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 <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<AudioDeviceModule>
|
||||||
|
CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput(
|
||||||
|
std::unique_ptr<AudioInput> audio_input,
|
||||||
|
std::unique_ptr<AudioOutput> audio_output);
|
||||||
|
|
||||||
|
} // namespace webrtc_win
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_AUDIO_DEVICE_WIN_AUDIO_DEVICE_MODULE_WIN_H_
|
||||||
439
modules/audio_device/win/core_audio_base_win.cc
Normal file
439
modules/audio_device/win/core_audio_base_win.cc
Normal file
@ -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 <string>
|
||||||
|
|
||||||
|
#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<CoreAudioBase*>(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<IAudioClient> 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<WORD>(params.channels());
|
||||||
|
format->nSamplesPerSec = params.sample_rate();
|
||||||
|
format->wBitsPerSample = rtc::dchecked_cast<WORD>(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<WORD>(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<double>(
|
||||||
|
core_audio_utility::ReferenceTimeToTimeDelta(device_period).ms()) /
|
||||||
|
1000.0L;
|
||||||
|
const int preferred_frames_per_buffer =
|
||||||
|
static_cast<int>(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<rtc::PlatformThread>(
|
||||||
|
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<ISimpleAudioVolume> 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
|
||||||
102
modules/audio_device/win/core_audio_base_win.h
Normal file
102
modules/audio_device/win/core_audio_base_win.h
Normal file
@ -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 <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<bool(uint64_t device_frequency)> 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<IAudioClient> audio_client_;
|
||||||
|
Microsoft::WRL::ComPtr<IAudioClock> audio_clock_;
|
||||||
|
ScopedHandle audio_samples_event_;
|
||||||
|
ScopedHandle stop_event_;
|
||||||
|
std::unique_ptr<rtc::PlatformThread> audio_thread_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void StopThread();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc_win
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_
|
||||||
342
modules/audio_device/win/core_audio_input_win.cc
Normal file
342
modules/audio_device/win/core_audio_input_win.cc
Normal file
@ -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<FineAudioBuffer>(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<IAudioCaptureClient> 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<double>(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<const int16_t*>(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<int> 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
|
||||||
67
modules/audio_device/win/core_audio_input_win.h
Normal file
67
modules/audio_device/win/core_audio_input_win.h
Normal file
@ -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 <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<int> EstimateLatencyMillis(uint64_t capture_time_100ns);
|
||||||
|
|
||||||
|
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
|
||||||
|
Microsoft::WRL::ComPtr<IAudioCaptureClient> audio_capture_client_;
|
||||||
|
rtc::Optional<double> qpc_to_100ns_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc_win
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_INPUT_WIN_H_
|
||||||
306
modules/audio_device/win/core_audio_output_win.cc
Normal file
306
modules/audio_device/win/core_audio_output_win.cc
Normal file
@ -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<FineAudioBuffer>(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<IAudioRenderClient> audio_render_client =
|
||||||
|
core_audio_utility::CreateRenderClient(audio_client_.Get());
|
||||||
|
if (!audio_render_client.Get())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
ComPtr<IAudioClock> 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<int16_t*>(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
|
||||||
66
modules/audio_device/win/core_audio_output_win.h
Normal file
66
modules/audio_device/win/core_audio_output_win.h
Normal file
@ -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 <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<FineAudioBuffer> fine_audio_buffer_;
|
||||||
|
Microsoft::WRL::ComPtr<IAudioRenderClient> audio_render_client_;
|
||||||
|
uint64_t num_frames_written_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc_win
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_OUTPUT_WIN_H_
|
||||||
@ -11,6 +11,7 @@
|
|||||||
#include "modules/audio_device/win/core_audio_utility_win.h"
|
#include "modules/audio_device/win/core_audio_utility_win.h"
|
||||||
|
|
||||||
#include <Functiondiscoverykeys_devpkey.h>
|
#include <Functiondiscoverykeys_devpkey.h>
|
||||||
|
#include <atlbase.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
|
|
||||||
@ -24,6 +25,7 @@
|
|||||||
#include "rtc_base/strings/string_builder.h"
|
#include "rtc_base/strings/string_builder.h"
|
||||||
#include "rtc_base/stringutils.h"
|
#include "rtc_base/stringutils.h"
|
||||||
|
|
||||||
|
using ATL::CComHeapPtr;
|
||||||
using Microsoft::WRL::ComPtr;
|
using Microsoft::WRL::ComPtr;
|
||||||
using webrtc::AudioDeviceName;
|
using webrtc::AudioDeviceName;
|
||||||
using webrtc::AudioParameters;
|
using webrtc::AudioParameters;
|
||||||
@ -44,13 +46,21 @@ bool LoadAudiosesDll() {
|
|||||||
nullptr);
|
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<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
|
ComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
|
||||||
bool allow_reinitialize) {
|
bool allow_reinitialize) {
|
||||||
ComPtr<IMMDeviceEnumerator> device_enumerator;
|
ComPtr<IMMDeviceEnumerator> device_enumerator;
|
||||||
_com_error error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
|
_com_error error =
|
||||||
CLSCTX_INPROC_SERVER,
|
::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
|
||||||
IID_PPV_ARGS(&device_enumerator));
|
IID_PPV_ARGS(&device_enumerator));
|
||||||
|
|
||||||
if (error.Error() != S_OK) {
|
if (error.Error() != S_OK) {
|
||||||
RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error);
|
RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error);
|
||||||
}
|
}
|
||||||
@ -64,8 +74,7 @@ ComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
|
|||||||
error = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
error = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
if (error.Error() != S_OK) {
|
if (error.Error() != S_OK) {
|
||||||
error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
|
error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
|
||||||
CLSCTX_INPROC_SERVER,
|
CLSCTX_ALL, IID_PPV_ARGS(&device_enumerator));
|
||||||
IID_PPV_ARGS(&device_enumerator));
|
|
||||||
if (error.Error() != S_OK) {
|
if (error.Error() != S_OK) {
|
||||||
RTC_LOG(LS_ERROR) << "CoCreateInstance failed: "
|
RTC_LOG(LS_ERROR) << "CoCreateInstance failed: "
|
||||||
<< ErrorToString(error);
|
<< ErrorToString(error);
|
||||||
@ -150,7 +159,7 @@ ComPtr<IMMDevice> CreateDeviceInternal(const std::string& device_id,
|
|||||||
std::string GetDeviceIdInternal(IMMDevice* device) {
|
std::string GetDeviceIdInternal(IMMDevice* device) {
|
||||||
// Retrieve unique name of endpoint device.
|
// Retrieve unique name of endpoint device.
|
||||||
// Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
|
// Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
|
||||||
ScopedCoMem<WCHAR> device_id;
|
CComHeapPtr<WCHAR> device_id;
|
||||||
if (SUCCEEDED(device->GetId(&device_id))) {
|
if (SUCCEEDED(device->GetId(&device_id))) {
|
||||||
return rtc::ToUtf8(device_id, wcslen(device_id));
|
return rtc::ToUtf8(device_id, wcslen(device_id));
|
||||||
} else {
|
} else {
|
||||||
@ -181,6 +190,45 @@ std::string GetDeviceFriendlyNameInternal(IMMDevice* device) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComPtr<IAudioSessionManager2> CreateSessionManager2Internal(
|
||||||
|
IMMDevice* audio_device) {
|
||||||
|
if (!audio_device)
|
||||||
|
return ComPtr<IAudioSessionManager2>();
|
||||||
|
|
||||||
|
ComPtr<IAudioSessionManager2> 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<IAudioSessionEnumerator> CreateSessionEnumeratorInternal(
|
||||||
|
IMMDevice* audio_device) {
|
||||||
|
if (!audio_device) {
|
||||||
|
return ComPtr<IAudioSessionEnumerator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ComPtr<IAudioSessionEnumerator> audio_session_enumerator;
|
||||||
|
ComPtr<IAudioSessionManager2> 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<IAudioSessionEnumerator>();
|
||||||
|
}
|
||||||
|
return audio_session_enumerator;
|
||||||
|
}
|
||||||
|
|
||||||
// Creates and activates an IAudioClient COM object given the selected
|
// Creates and activates an IAudioClient COM object given the selected
|
||||||
// endpoint device.
|
// endpoint device.
|
||||||
ComPtr<IAudioClient> CreateClientInternal(IMMDevice* audio_device) {
|
ComPtr<IAudioClient> CreateClientInternal(IMMDevice* audio_device) {
|
||||||
@ -188,8 +236,8 @@ ComPtr<IAudioClient> CreateClientInternal(IMMDevice* audio_device) {
|
|||||||
return ComPtr<IAudioClient>();
|
return ComPtr<IAudioClient>();
|
||||||
|
|
||||||
ComPtr<IAudioClient> audio_client;
|
ComPtr<IAudioClient> audio_client;
|
||||||
_com_error error = audio_device->Activate(
|
_com_error error = audio_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
|
||||||
__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, &audio_client);
|
nullptr, &audio_client);
|
||||||
if (error.Error() != S_OK) {
|
if (error.Error() != S_OK) {
|
||||||
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient) failed: "
|
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient) failed: "
|
||||||
<< ErrorToString(error);
|
<< ErrorToString(error);
|
||||||
@ -202,8 +250,8 @@ ComPtr<IAudioClient2> CreateClient2Internal(IMMDevice* audio_device) {
|
|||||||
return ComPtr<IAudioClient2>();
|
return ComPtr<IAudioClient2>();
|
||||||
|
|
||||||
ComPtr<IAudioClient2> audio_client;
|
ComPtr<IAudioClient2> audio_client;
|
||||||
_com_error error = audio_device->Activate(
|
_com_error error = audio_device->Activate(__uuidof(IAudioClient2), CLSCTX_ALL,
|
||||||
__uuidof(IAudioClient2), CLSCTX_INPROC_SERVER, nullptr, &audio_client);
|
nullptr, &audio_client);
|
||||||
if (error.Error() != S_OK) {
|
if (error.Error() != S_OK) {
|
||||||
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient2) failed: "
|
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient2) failed: "
|
||||||
<< ErrorToString(error);
|
<< ErrorToString(error);
|
||||||
@ -346,10 +394,16 @@ HRESULT GetPreferredAudioParametersInternal(IAudioClient* client,
|
|||||||
namespace core_audio_utility {
|
namespace core_audio_utility {
|
||||||
|
|
||||||
bool IsSupported() {
|
bool IsSupported() {
|
||||||
|
RTC_DLOG(INFO) << "IsSupported";
|
||||||
static bool g_is_supported = IsSupportedInternal();
|
static bool g_is_supported = IsSupportedInternal();
|
||||||
return g_is_supported;
|
return g_is_supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsMMCSSSupported() {
|
||||||
|
RTC_DLOG(INFO) << "IsMMCSSSupported";
|
||||||
|
return LoadAvrtDll();
|
||||||
|
}
|
||||||
|
|
||||||
int NumberOfActiveDevices(EDataFlow data_flow) {
|
int NumberOfActiveDevices(EDataFlow data_flow) {
|
||||||
// Generate a collection of active audio endpoint devices for the specified
|
// Generate a collection of active audio endpoint devices for the specified
|
||||||
// data-flow direction.
|
// data-flow direction.
|
||||||
@ -468,6 +522,67 @@ bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names) {
|
|||||||
return GetDeviceNamesInternal(eRender, device_names);
|
return GetDeviceNamesInternal(eRender, device_names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComPtr<IAudioSessionManager2> CreateSessionManager2(IMMDevice* device) {
|
||||||
|
RTC_DLOG(INFO) << "CreateSessionManager2";
|
||||||
|
return CreateSessionManager2Internal(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<IAudioSessionEnumerator> CreateSessionEnumerator(
|
||||||
|
IMMDevice* device) {
|
||||||
|
RTC_DLOG(INFO) << "CreateSessionEnumerator";
|
||||||
|
return CreateSessionEnumeratorInternal(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NumberOfActiveSessions(IMMDevice* device) {
|
||||||
|
RTC_DLOG(INFO) << "NumberOfActiveSessions";
|
||||||
|
ComPtr<IAudioSessionEnumerator> 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<IAudioSessionControl> 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<WCHAR> 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<IAudioClient> CreateClient(const std::string& device_id,
|
ComPtr<IAudioClient> CreateClient(const std::string& device_id,
|
||||||
EDataFlow data_flow,
|
EDataFlow data_flow,
|
||||||
ERole role) {
|
ERole role) {
|
||||||
@ -624,11 +739,12 @@ HRESULT SharedModeInitialize(IAudioClient* client,
|
|||||||
uint32_t* endpoint_buffer_size) {
|
uint32_t* endpoint_buffer_size) {
|
||||||
RTC_DLOG(INFO) << "SharedModeInitialize";
|
RTC_DLOG(INFO) << "SharedModeInitialize";
|
||||||
RTC_DCHECK(client);
|
RTC_DCHECK(client);
|
||||||
// Use default flags (i.e, don't set AUDCLNT_STREAMFLAGS_NOPERSIST) to
|
// The AUDCLNT_STREAMFLAGS_NOPERSIST flag disables persistence of the volume
|
||||||
// ensure that the volume level and muting state for a rendering session
|
// and mute settings for a session that contains rendering streams.
|
||||||
// are persistent across system restarts. The volume level and muting
|
// By default, the volume level and muting state for a rendering session are
|
||||||
// state for a capture session are never persistent.
|
// persistent across system restarts. The volume level and muting state for a
|
||||||
DWORD stream_flags = 0;
|
// capture session are never persistent.
|
||||||
|
DWORD stream_flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
|
||||||
|
|
||||||
// Enable event-driven streaming if a valid event handle is provided.
|
// Enable event-driven streaming if a valid event handle is provided.
|
||||||
// After the stream starts, the audio engine will signal the event handle
|
// After the stream starts, the audio engine will signal the event handle
|
||||||
@ -739,6 +855,22 @@ ComPtr<IAudioClock> CreateAudioClock(IAudioClient* client) {
|
|||||||
return audio_clock;
|
return audio_clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComPtr<ISimpleAudioVolume> 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<ISimpleAudioVolume> 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<ISimpleAudioVolume>();
|
||||||
|
}
|
||||||
|
return simple_audio_volume;
|
||||||
|
}
|
||||||
|
|
||||||
bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
|
bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
|
||||||
IAudioRenderClient* render_client) {
|
IAudioRenderClient* render_client) {
|
||||||
RTC_DLOG(INFO) << "FillRenderEndpointBufferWithSilence";
|
RTC_DLOG(INFO) << "FillRenderEndpointBufferWithSilence";
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_UTILITY_WIN_H_
|
#define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_UTILITY_WIN_H_
|
||||||
|
|
||||||
#include <Audioclient.h>
|
#include <Audioclient.h>
|
||||||
|
#include <Audiopolicy.h>
|
||||||
#include <Mmdeviceapi.h>
|
#include <Mmdeviceapi.h>
|
||||||
#include <avrt.h>
|
#include <avrt.h>
|
||||||
#include <comdef.h>
|
#include <comdef.h>
|
||||||
@ -26,6 +27,8 @@
|
|||||||
#include "modules/audio_device/include/audio_device_defines.h"
|
#include "modules/audio_device/include/audio_device_defines.h"
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
|
|
||||||
|
#pragma comment(lib, "Avrt.lib")
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace webrtc_win {
|
namespace webrtc_win {
|
||||||
|
|
||||||
@ -78,6 +81,10 @@ class ScopedMMCSSRegistration {
|
|||||||
class ScopedCOMInitializer {
|
class ScopedCOMInitializer {
|
||||||
public:
|
public:
|
||||||
// Enum value provided to initialize the thread as an MTA instead of STA.
|
// 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 };
|
enum SelectMTA { kMTA };
|
||||||
|
|
||||||
// Constructor for STA initialization.
|
// Constructor for STA initialization.
|
||||||
@ -107,9 +114,23 @@ class ScopedCOMInitializer {
|
|||||||
void Initialize(COINIT init) {
|
void Initialize(COINIT init) {
|
||||||
// Initializes the COM library for use by the calling thread, sets the
|
// Initializes the COM library for use by the calling thread, sets the
|
||||||
// thread's concurrency model, and creates a new apartment for the thread
|
// 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);
|
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_;
|
HRESULT hr_;
|
||||||
};
|
};
|
||||||
@ -263,6 +284,12 @@ namespace core_audio_utility {
|
|||||||
// other methods in this class.
|
// other methods in this class.
|
||||||
bool IsSupported();
|
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
|
// The MMDevice API lets clients discover the audio endpoint devices in the
|
||||||
// system and determine which devices are suitable for the application to use.
|
// system and determine which devices are suitable for the application to use.
|
||||||
// Header file Mmdeviceapi.h defines the interfaces in the MMDevice API.
|
// 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
|
// device. Header files Audioclient.h and Audiopolicy.h define the WASAPI
|
||||||
// interfaces.
|
// 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<IAudioSessionManager2> 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<IAudioSessionEnumerator> 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.
|
// device specified by data-flow direction and role.
|
||||||
Microsoft::WRL::ComPtr<IAudioClient> CreateClient(const std::string& device_id,
|
Microsoft::WRL::ComPtr<IAudioClient> CreateClient(const std::string& device_id,
|
||||||
EDataFlow data_flow,
|
EDataFlow data_flow,
|
||||||
@ -384,7 +426,6 @@ HRESULT GetPreferredAudioParameters(IAudioClient* client,
|
|||||||
// TODO(henrika):
|
// TODO(henrika):
|
||||||
// - use IAudioClient2::SetClientProperties before calling this method
|
// - use IAudioClient2::SetClientProperties before calling this method
|
||||||
// - IAudioClient::Initialize(your_format, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
// - IAudioClient::Initialize(your_format, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
||||||
// |
|
|
||||||
// AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY)
|
// AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY)
|
||||||
HRESULT SharedModeInitialize(IAudioClient* client,
|
HRESULT SharedModeInitialize(IAudioClient* client,
|
||||||
const WAVEFORMATEXTENSIBLE* format,
|
const WAVEFORMATEXTENSIBLE* format,
|
||||||
@ -410,12 +451,17 @@ Microsoft::WRL::ComPtr<IAudioCaptureClient> CreateCaptureClient(
|
|||||||
// data rate and the current position in the stream.
|
// data rate and the current position in the stream.
|
||||||
Microsoft::WRL::ComPtr<IAudioClock> CreateAudioClock(IAudioClient* client);
|
Microsoft::WRL::ComPtr<IAudioClock> 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<ISimpleAudioVolume> CreateSimpleAudioVolume(
|
||||||
|
IAudioClient* client);
|
||||||
|
|
||||||
// Fills up the endpoint rendering buffer with silence for an existing
|
// Fills up the endpoint rendering buffer with silence for an existing
|
||||||
// IAudioClient given by |client| and a corresponding IAudioRenderClient
|
// IAudioClient given by |client| and a corresponding IAudioRenderClient
|
||||||
// given by |render_client|.
|
// given by |render_client|.
|
||||||
bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
|
bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
|
||||||
IAudioRenderClient* render_client);
|
IAudioRenderClient* render_client);
|
||||||
|
|
||||||
// Transforms a WAVEFORMATEXTENSIBLE struct to a human-readable string.
|
// Transforms a WAVEFORMATEXTENSIBLE struct to a human-readable string.
|
||||||
std::string WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format);
|
std::string WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format);
|
||||||
|
|
||||||
|
|||||||
@ -11,8 +11,11 @@
|
|||||||
#include "modules/audio_device/win/core_audio_utility_win.h"
|
#include "modules/audio_device/win/core_audio_utility_win.h"
|
||||||
#include "rtc_base/arraysize.h"
|
#include "rtc_base/arraysize.h"
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
|
#include "rtc_base/win/windows_version.h"
|
||||||
#include "test/gtest.h"
|
#include "test/gtest.h"
|
||||||
|
|
||||||
|
#include "system_wrappers/include/sleep.h"
|
||||||
|
|
||||||
using Microsoft::WRL::ComPtr;
|
using Microsoft::WRL::ComPtr;
|
||||||
using webrtc::AudioDeviceName;
|
using webrtc::AudioDeviceName;
|
||||||
|
|
||||||
@ -53,7 +56,8 @@ bool ShouldAbortTest(bool requirements_satisfied,
|
|||||||
// CoreAudioUtilityWinTest test fixture.
|
// CoreAudioUtilityWinTest test fixture.
|
||||||
class CoreAudioUtilityWinTest : public ::testing::Test {
|
class CoreAudioUtilityWinTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
CoreAudioUtilityWinTest() {
|
CoreAudioUtilityWinTest()
|
||||||
|
: com_init_(webrtc_win::ScopedCOMInitializer::kMTA) {
|
||||||
// We must initialize the COM library on a thread before we calling any of
|
// 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
|
// the library functions. All COM functions will return CO_E_NOTINITIALIZED
|
||||||
// otherwise.
|
// otherwise.
|
||||||
@ -242,31 +246,107 @@ TEST_F(CoreAudioUtilityWinTest, GetOutputDeviceNames) {
|
|||||||
2 + core_audio_utility::NumberOfActiveDevices(eRender));
|
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<IMMDevice> device(core_audio_utility::CreateDevice(
|
||||||
|
AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole));
|
||||||
|
EXPECT_TRUE(device.Get());
|
||||||
|
ComPtr<IAudioSessionManager2> 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<IMMDevice> device(core_audio_utility::CreateDevice(
|
||||||
|
AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole));
|
||||||
|
EXPECT_TRUE(device.Get());
|
||||||
|
ComPtr<IAudioSessionEnumerator> 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<IMMDevice> 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<IAudioSessionEnumerator> 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) {
|
TEST_F(CoreAudioUtilityWinTest, CreateClient) {
|
||||||
ABORT_TEST_IF_NOT(DevicesAvailable());
|
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
|
// Obtain reference to an IAudioClient interface for a default audio endpoint
|
||||||
// device specified by two different data flows and the |eConsole| role.
|
// 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<IAudioClient> client = core_audio_utility::CreateClient(
|
ComPtr<IAudioClient> client = core_audio_utility::CreateClient(
|
||||||
AudioDeviceName::kDefaultDeviceId, data[i], eConsole);
|
AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole);
|
||||||
EXPECT_TRUE(client.Get());
|
EXPECT_TRUE(client.Get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(henrik): enable when support for Windows version querying is added.
|
TEST_F(CoreAudioUtilityWinTest, CreateClient2) {
|
||||||
TEST_F(CoreAudioUtilityWinTest, DISABLED_CreateClient2) {
|
ABORT_TEST_IF_NOT(DevicesAvailable() &&
|
||||||
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
|
// Obtain reference to an IAudioClient2 interface for a default audio endpoint
|
||||||
// device specified by two different data flows and the |eConsole| role.
|
// 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<IAudioClient2> client = core_audio_utility::CreateClient2(
|
ComPtr<IAudioClient2> client = core_audio_utility::CreateClient2(
|
||||||
AudioDeviceName::kDefaultDeviceId, data[i], eConsole);
|
AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole);
|
||||||
EXPECT_TRUE(client.Get());
|
EXPECT_TRUE(client.Get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,16 +405,16 @@ TEST_F(CoreAudioUtilityWinTest, IsFormatSupported) {
|
|||||||
TEST_F(CoreAudioUtilityWinTest, GetDevicePeriod) {
|
TEST_F(CoreAudioUtilityWinTest, GetDevicePeriod) {
|
||||||
ABORT_TEST_IF_NOT(DevicesAvailable());
|
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
|
// Verify that the device periods are valid for the default render and
|
||||||
// capture devices.
|
// capture devices.
|
||||||
for (size_t i = 0; i < arraysize(data); ++i) {
|
for (size_t i = 0; i < arraysize(data_flow); ++i) {
|
||||||
ComPtr<IAudioClient> client;
|
ComPtr<IAudioClient> client;
|
||||||
REFERENCE_TIME shared_time_period = 0;
|
REFERENCE_TIME shared_time_period = 0;
|
||||||
REFERENCE_TIME exclusive_time_period = 0;
|
REFERENCE_TIME exclusive_time_period = 0;
|
||||||
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
|
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
|
||||||
data[i], eConsole);
|
data_flow[i], eConsole);
|
||||||
EXPECT_TRUE(client.Get());
|
EXPECT_TRUE(client.Get());
|
||||||
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetDevicePeriod(
|
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetDevicePeriod(
|
||||||
client.Get(), AUDCLNT_SHAREMODE_SHARED, &shared_time_period)));
|
client.Get(), AUDCLNT_SHAREMODE_SHARED, &shared_time_period)));
|
||||||
@ -349,25 +429,25 @@ TEST_F(CoreAudioUtilityWinTest, GetDevicePeriod) {
|
|||||||
TEST_F(CoreAudioUtilityWinTest, GetPreferredAudioParameters) {
|
TEST_F(CoreAudioUtilityWinTest, GetPreferredAudioParameters) {
|
||||||
ABORT_TEST_IF_NOT(DevicesAvailable());
|
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
|
// Verify that the preferred audio parameters are OK for the default render
|
||||||
// and capture devices.
|
// 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;
|
webrtc::AudioParameters params;
|
||||||
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetPreferredAudioParameters(
|
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_valid());
|
||||||
EXPECT_TRUE(params.is_complete());
|
EXPECT_TRUE(params.is_complete());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the preferred audio parameters are OK for the default
|
// Verify that the preferred audio parameters are OK for the default
|
||||||
// communication devices.
|
// communication devices.
|
||||||
for (size_t i = 0; i < arraysize(data); ++i) {
|
for (size_t i = 0; i < arraysize(data_flow); ++i) {
|
||||||
webrtc::AudioParameters params;
|
webrtc::AudioParameters params;
|
||||||
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetPreferredAudioParameters(
|
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetPreferredAudioParameters(
|
||||||
AudioDeviceName::kDefaultCommunicationsDeviceId, data[i] == eRender,
|
AudioDeviceName::kDefaultCommunicationsDeviceId,
|
||||||
¶ms)));
|
data_flow[i] == eRender, ¶ms)));
|
||||||
EXPECT_TRUE(params.is_valid());
|
EXPECT_TRUE(params.is_valid());
|
||||||
EXPECT_TRUE(params.is_complete());
|
EXPECT_TRUE(params.is_complete());
|
||||||
}
|
}
|
||||||
@ -443,23 +523,23 @@ TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) {
|
|||||||
TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
|
TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
|
||||||
ABORT_TEST_IF_NOT(DevicesAvailable());
|
ABORT_TEST_IF_NOT(DevicesAvailable());
|
||||||
|
|
||||||
EDataFlow data[] = {eRender, eCapture};
|
EDataFlow data_flow[] = {eRender, eCapture};
|
||||||
|
|
||||||
WAVEFORMATPCMEX format;
|
WAVEFORMATPCMEX format;
|
||||||
uint32_t endpoint_buffer_size = 0;
|
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<IAudioClient> client;
|
ComPtr<IAudioClient> client;
|
||||||
ComPtr<IAudioRenderClient> render_client;
|
ComPtr<IAudioRenderClient> render_client;
|
||||||
ComPtr<IAudioCaptureClient> capture_client;
|
ComPtr<IAudioCaptureClient> capture_client;
|
||||||
|
|
||||||
// Create a default client for the given data-flow direction.
|
// Create a default client for the given data-flow direction.
|
||||||
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
|
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
|
||||||
data[i], eConsole);
|
data_flow[i], eConsole);
|
||||||
EXPECT_TRUE(client.Get());
|
EXPECT_TRUE(client.Get());
|
||||||
EXPECT_TRUE(SUCCEEDED(
|
EXPECT_TRUE(SUCCEEDED(
|
||||||
core_audio_utility::GetSharedModeMixFormat(client.Get(), &format)));
|
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
|
// It is not possible to create a render client using an unitialized
|
||||||
// client interface.
|
// client interface.
|
||||||
render_client = core_audio_utility::CreateRenderClient(client.Get());
|
render_client = core_audio_utility::CreateRenderClient(client.Get());
|
||||||
@ -471,7 +551,7 @@ TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
|
|||||||
render_client = core_audio_utility::CreateRenderClient(client.Get());
|
render_client = core_audio_utility::CreateRenderClient(client.Get());
|
||||||
EXPECT_TRUE(render_client.Get());
|
EXPECT_TRUE(render_client.Get());
|
||||||
EXPECT_GT(endpoint_buffer_size, 0u);
|
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
|
// It is not possible to create a capture client using an unitialized
|
||||||
// client interface.
|
// client interface.
|
||||||
capture_client = core_audio_utility::CreateCaptureClient(client.Get());
|
capture_client = core_audio_utility::CreateCaptureClient(client.Get());
|
||||||
@ -490,18 +570,18 @@ TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
|
|||||||
TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) {
|
TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) {
|
||||||
ABORT_TEST_IF_NOT(DevicesAvailable());
|
ABORT_TEST_IF_NOT(DevicesAvailable());
|
||||||
|
|
||||||
EDataFlow data[] = {eRender, eCapture};
|
EDataFlow data_flow[] = {eRender, eCapture};
|
||||||
|
|
||||||
WAVEFORMATPCMEX format;
|
WAVEFORMATPCMEX format;
|
||||||
uint32_t endpoint_buffer_size = 0;
|
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<IAudioClient> client;
|
ComPtr<IAudioClient> client;
|
||||||
ComPtr<IAudioClock> audio_clock;
|
ComPtr<IAudioClock> audio_clock;
|
||||||
|
|
||||||
// Create a default client for the given data-flow direction.
|
// Create a default client for the given data-flow direction.
|
||||||
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
|
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
|
||||||
data[i], eConsole);
|
data_flow[i], eConsole);
|
||||||
EXPECT_TRUE(client.Get());
|
EXPECT_TRUE(client.Get());
|
||||||
EXPECT_TRUE(SUCCEEDED(
|
EXPECT_TRUE(SUCCEEDED(
|
||||||
core_audio_utility::GetSharedModeMixFormat(client.Get(), &format)));
|
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<IAudioClient> client;
|
||||||
|
ComPtr<ISimpleAudioVolume> 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) {
|
TEST_F(CoreAudioUtilityWinTest, FillRenderEndpointBufferWithSilence) {
|
||||||
ABORT_TEST_IF_NOT(DevicesAvailable());
|
ABORT_TEST_IF_NOT(DevicesAvailable());
|
||||||
|
|
||||||
|
|||||||
@ -31,8 +31,12 @@ namespace {
|
|||||||
// implementations.
|
// implementations.
|
||||||
//
|
//
|
||||||
// An instance can be created on any thread, but must then be used on one and
|
// 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
|
// the same thread. All public methods must also be called on the same thread.
|
||||||
// thread checker will RTC_DCHECK if any method is called on an invalid 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 {
|
class AndroidAudioDeviceModule : public AudioDeviceModule {
|
||||||
public:
|
public:
|
||||||
// For use with UMA logging. Must be kept in sync with histograms.xml in
|
// For use with UMA logging. Must be kept in sync with histograms.xml in
|
||||||
|
|||||||
Reference in New Issue
Block a user