Adds stream-switch support in new Windows ADM.

Second round of the new Windows ADM is now ready for review. Main
changes are:

Supports internal (automatic) restart of audio streams when an active
audio stream disconnects (happens when a device is removed).

Adds support for IAudioClient3 and IAudioClient2 for platforms which
supports it (>Win8 and >Win10).

Modifies the threading model to support restart "from the inside" on
the native audio thread.

Adds two new test methods for the ADM to emulate restart events or
stream-switch events.

Adds two new test methods to support rate conversion to ensure that
audio can be tested in loopback even if devices runs at different
sample rates.

Added initial components for low-latency support. Verified that it works
but disabled it with a flag for now.

Bug: webrtc:9265
Change-Id: Ia8e577daabea6b433f2c2eabab4e46ce8added6a
Reviewed-on: https://webrtc-review.googlesource.com/86020
Reviewed-by: Oskar Sundbom <ossu@webrtc.org>
Commit-Queue: Henrik Andreassson <henrika@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24578}
This commit is contained in:
henrika
2018-09-05 14:34:40 +02:00
committed by Commit Bot
parent e10f3b7403
commit 5b6afc0ce6
20 changed files with 1805 additions and 264 deletions

View File

@ -20,6 +20,7 @@
#include "rtc_base/checks.h" #include "rtc_base/checks.h"
#include "rtc_base/format_macros.h" #include "rtc_base/format_macros.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/timeutils.h" #include "rtc_base/timeutils.h"
#include "system_wrappers/include/metrics.h" #include "system_wrappers/include/metrics.h"

View File

@ -68,10 +68,16 @@
namespace webrtc { namespace webrtc {
// static
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__;
return AudioDeviceModule::CreateForTest(audio_layer);
}
// static
rtc::scoped_refptr<AudioDeviceModuleForTest> AudioDeviceModule::CreateForTest(
const AudioLayer audio_layer) {
RTC_LOG(INFO) << __FUNCTION__;
// The "AudioDeviceModule::kWindowsCoreAudio2" audio layer has its own // The "AudioDeviceModule::kWindowsCoreAudio2" audio layer has its own
// dedicated factory method which should be used instead. // dedicated factory method which should be used instead.

View File

@ -25,7 +25,7 @@ namespace webrtc {
class AudioDeviceGeneric; class AudioDeviceGeneric;
class AudioManager; class AudioManager;
class AudioDeviceModuleImpl : public AudioDeviceModule { class AudioDeviceModuleImpl : public AudioDeviceModuleForTest {
public: public:
enum PlatformType { enum PlatformType {
kPlatformNotSupported = 0, kPlatformNotSupported = 0,
@ -148,6 +148,11 @@ class AudioDeviceModuleImpl : public AudioDeviceModule {
#endif #endif
AudioDeviceBuffer* GetAudioDeviceBuffer() { return &audio_device_buffer_; } AudioDeviceBuffer* GetAudioDeviceBuffer() { return &audio_device_buffer_; }
int RestartPlayoutInternally() override { return -1; }
int RestartRecordingInternally() override { return -1; }
int SetPlayoutSampleRate(uint32_t sample_rate) override { return -1; }
int SetRecordingSampleRate(uint32_t sample_rate) override { return -1; }
private: private:
PlatformType Platform() const; PlatformType Platform() const;
AudioLayer PlatformAudioLayer() const; AudioLayer PlatformAudioLayer() const;

View File

@ -28,6 +28,7 @@
#include "rtc_base/thread_annotations.h" #include "rtc_base/thread_annotations.h"
#include "rtc_base/thread_checker.h" #include "rtc_base/thread_checker.h"
#include "rtc_base/timeutils.h" #include "rtc_base/timeutils.h"
#include "system_wrappers/include/sleep.h"
#include "test/gmock.h" #include "test/gmock.h"
#include "test/gtest.h" #include "test/gtest.h"
#ifdef WEBRTC_WIN #ifdef WEBRTC_WIN
@ -41,6 +42,7 @@ using ::testing::Ge;
using ::testing::Invoke; using ::testing::Invoke;
using ::testing::NiceMock; using ::testing::NiceMock;
using ::testing::NotNull; using ::testing::NotNull;
using ::testing::Mock;
namespace webrtc { namespace webrtc {
namespace { namespace {
@ -330,6 +332,14 @@ class MockAudioTransport : public test::MockAudioTransport {
} }
} }
// Special constructor used in manual tests where the user wants to run audio
// until e.g. a keyboard key is pressed. The event flag is set to nullptr by
// default since it is up to the user to stop the test. See e.g.
// DISABLED_RunPlayoutAndRecordingInFullDuplexAndWaitForEnterKey().
void HandleCallbacks(AudioStream* audio_stream) {
HandleCallbacks(nullptr, audio_stream, 0);
}
int32_t RealRecordedDataIsAvailable(const void* audio_buffer, int32_t RealRecordedDataIsAvailable(const void* audio_buffer,
const size_t samples_per_channel, const size_t samples_per_channel,
const size_t bytes_per_frame, const size_t bytes_per_frame,
@ -341,7 +351,6 @@ class MockAudioTransport : public test::MockAudioTransport {
const bool typing_status, const bool typing_status,
uint32_t& new_mic_level) { uint32_t& new_mic_level) {
EXPECT_TRUE(rec_mode()) << "No test is expecting these callbacks."; EXPECT_TRUE(rec_mode()) << "No test is expecting these callbacks.";
RTC_LOG(INFO) << "+";
// Store audio parameters once in the first callback. For all other // Store audio parameters once in the first callback. For all other
// callbacks, verify that the provided audio parameters are maintained and // callbacks, verify that the provided audio parameters are maintained and
// that each callback corresponds to 10ms for any given sample rate. // that each callback corresponds to 10ms for any given sample rate.
@ -364,7 +373,7 @@ class MockAudioTransport : public test::MockAudioTransport {
samples_per_channel * channels)); samples_per_channel * channels));
} }
// Signal the event after given amount of callbacks. // Signal the event after given amount of callbacks.
if (ReceivedEnoughCallbacks()) { if (event_ && ReceivedEnoughCallbacks()) {
event_->Set(); event_->Set();
} }
return 0; return 0;
@ -379,7 +388,6 @@ class MockAudioTransport : public test::MockAudioTransport {
int64_t* elapsed_time_ms, int64_t* elapsed_time_ms,
int64_t* ntp_time_ms) { int64_t* ntp_time_ms) {
EXPECT_TRUE(play_mode()) << "No test is expecting these callbacks."; EXPECT_TRUE(play_mode()) << "No test is expecting these callbacks.";
RTC_LOG(INFO) << "-";
// Store audio parameters once in the first callback. For all other // Store audio parameters once in the first callback. For all other
// callbacks, verify that the provided audio parameters are maintained and // callbacks, verify that the provided audio parameters are maintained and
// that each callback corresponds to 10ms for any given sample rate. // that each callback corresponds to 10ms for any given sample rate.
@ -406,7 +414,7 @@ class MockAudioTransport : public test::MockAudioTransport {
std::memset(audio_buffer, 0, num_bytes); std::memset(audio_buffer, 0, num_bytes);
} }
// Signal the event after given amount of callbacks. // Signal the event after given amount of callbacks.
if (ReceivedEnoughCallbacks()) { if (event_ && ReceivedEnoughCallbacks()) {
event_->Set(); event_->Set();
} }
return 0; return 0;
@ -438,6 +446,15 @@ class MockAudioTransport : public test::MockAudioTransport {
type_ == TransportType::kPlayAndRecord; type_ == TransportType::kPlayAndRecord;
} }
void ResetCallbackCounters() {
if (play_mode()) {
play_count_ = 0;
}
if (rec_mode()) {
rec_count_ = 0;
}
}
private: private:
TransportType type_ = TransportType::kInvalid; TransportType type_ = TransportType::kInvalid;
rtc::Event* event_ = nullptr; rtc::Event* event_ = nullptr;
@ -506,18 +523,22 @@ class AudioDeviceTest
bool requirements_satisfied() const { return requirements_satisfied_; } bool requirements_satisfied() const { return requirements_satisfied_; }
rtc::Event* event() { return &event_; } rtc::Event* event() { return &event_; }
AudioDeviceModule::AudioLayer audio_layer() const { return audio_layer_; }
const rtc::scoped_refptr<AudioDeviceModule>& audio_device() const { // AudioDeviceModuleForTest extends the default ADM interface with some extra
// test methods. Intended for usage in tests only and requires a unique
// factory method. See CreateAudioDevice() for details.
const rtc::scoped_refptr<AudioDeviceModuleForTest>& audio_device() const {
return audio_device_; return audio_device_;
} }
rtc::scoped_refptr<AudioDeviceModule> CreateAudioDevice() { rtc::scoped_refptr<AudioDeviceModuleForTest> CreateAudioDevice() {
// Use the default factory for kPlatformDefaultAudio and a special factory // Use the default factory for kPlatformDefaultAudio and a special factory
// CreateWindowsCoreAudioAudioDeviceModule() for kWindowsCoreAudio2. // CreateWindowsCoreAudioAudioDeviceModuleForTest() for kWindowsCoreAudio2.
// The value of |audio_layer_| is set at construction by GetParam() and two // The value of |audio_layer_| is set at construction by GetParam() and two
// different layers are tested on Windows only. // different layers are tested on Windows only.
if (audio_layer_ == AudioDeviceModule::kPlatformDefaultAudio) { if (audio_layer_ == AudioDeviceModule::kPlatformDefaultAudio) {
return AudioDeviceModule::Create(audio_layer_); return AudioDeviceModule::CreateForTest(audio_layer_);
} else if (audio_layer_ == AudioDeviceModule::kWindowsCoreAudio2) { } else if (audio_layer_ == AudioDeviceModule::kWindowsCoreAudio2) {
#ifdef WEBRTC_WIN #ifdef WEBRTC_WIN
// 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
@ -528,7 +549,7 @@ class AudioDeviceTest
EXPECT_TRUE(com_initializer_->Succeeded()); EXPECT_TRUE(com_initializer_->Succeeded());
EXPECT_TRUE(webrtc_win::core_audio_utility::IsSupported()); EXPECT_TRUE(webrtc_win::core_audio_utility::IsSupported());
EXPECT_TRUE(webrtc_win::core_audio_utility::IsMMCSSSupported()); EXPECT_TRUE(webrtc_win::core_audio_utility::IsMMCSSSupported());
return CreateWindowsCoreAudioAudioDeviceModule(); return CreateWindowsCoreAudioAudioDeviceModuleForTest();
#else #else
return nullptr; return nullptr;
#endif #endif
@ -587,7 +608,7 @@ class AudioDeviceTest
AudioDeviceModule::AudioLayer audio_layer_; 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<AudioDeviceModuleForTest> audio_device_;
bool stereo_playout_ = false; bool stereo_playout_ = false;
}; };
@ -777,6 +798,124 @@ TEST_P(AudioDeviceTest, InitStopInitPlayoutWhileRecording) {
StopRecording(); StopRecording();
} }
// TODO(henrika): restart without intermediate destruction is currently only
// supported on Windows.
#ifdef WEBRTC_WIN
// Tests Start/Stop playout followed by a second session (emulates a restart
// triggered by a user using public APIs).
TEST_P(AudioDeviceTest, StartStopPlayoutWithExternalRestart) {
SKIP_TEST_IF_NOT(requirements_satisfied());
StartPlayout();
StopPlayout();
// Restart playout without destroying the ADM in between. Ensures that we
// support: Init(), Start(), Stop(), Init(), Start(), Stop().
StartPlayout();
StopPlayout();
}
// Tests Start/Stop recording followed by a second session (emulates a restart
// triggered by a user using public APIs).
TEST_P(AudioDeviceTest, StartStopRecordingWithExternalRestart) {
SKIP_TEST_IF_NOT(requirements_satisfied());
StartRecording();
StopRecording();
// Restart recording without destroying the ADM in between. Ensures that we
// support: Init(), Start(), Stop(), Init(), Start(), Stop().
StartRecording();
StopRecording();
}
// Tests Start/Stop playout followed by a second session (emulates a restart
// triggered by an internal callback e.g. corresponding to a device switch).
// Note that, internal restart is only supported in combination with the latest
// Windows ADM.
TEST_P(AudioDeviceTest, StartStopPlayoutWithInternalRestart) {
SKIP_TEST_IF_NOT(requirements_satisfied());
if (audio_layer() != AudioDeviceModule::kWindowsCoreAudio2) {
return;
}
MockAudioTransport mock(TransportType::kPlay);
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _))
.Times(AtLeast(kNumCallbacks));
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
StartPlayout();
event()->Wait(kTestTimeOutInMilliseconds);
EXPECT_TRUE(audio_device()->Playing());
// Restart playout but without stopping the internal audio thread.
// This procedure uses a non-public test API and it emulates what happens
// inside the ADM when e.g. a device is removed.
EXPECT_EQ(0, audio_device()->RestartPlayoutInternally());
// Run basic tests of public APIs while a restart attempt is active.
// These calls should now be very thin and not trigger any new actions.
EXPECT_EQ(-1, audio_device()->StopPlayout());
EXPECT_TRUE(audio_device()->Playing());
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
EXPECT_EQ(0, audio_device()->InitPlayout());
EXPECT_EQ(0, audio_device()->StartPlayout());
// Wait until audio has restarted and a new sequence of audio callbacks
// becomes active.
// TODO(henrika): is it possible to verify that the internal state transition
// is Stop->Init->Start?
ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock));
mock.ResetCallbackCounters();
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _))
.Times(AtLeast(kNumCallbacks));
event()->Wait(kTestTimeOutInMilliseconds);
EXPECT_TRUE(audio_device()->Playing());
// Stop playout and the audio thread after successful internal restart.
StopPlayout();
}
// Tests Start/Stop recording followed by a second session (emulates a restart
// triggered by an internal callback e.g. corresponding to a device switch).
// Note that, internal restart is only supported in combination with the latest
// Windows ADM.
TEST_P(AudioDeviceTest, StartStopRecordingWithInternalRestart) {
SKIP_TEST_IF_NOT(requirements_satisfied());
if (audio_layer() != AudioDeviceModule::kWindowsCoreAudio2) {
return;
}
MockAudioTransport mock(TransportType::kRecord);
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _,
false, _))
.Times(AtLeast(kNumCallbacks));
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
StartRecording();
event()->Wait(kTestTimeOutInMilliseconds);
EXPECT_TRUE(audio_device()->Recording());
// Restart recording but without stopping the internal audio thread.
// This procedure uses a non-public test API and it emulates what happens
// inside the ADM when e.g. a device is removed.
EXPECT_EQ(0, audio_device()->RestartRecordingInternally());
// Run basic tests of public APIs while a restart attempt is active.
// These calls should now be very thin and not trigger any new actions.
EXPECT_EQ(-1, audio_device()->StopRecording());
EXPECT_TRUE(audio_device()->Recording());
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
EXPECT_EQ(0, audio_device()->InitRecording());
EXPECT_EQ(0, audio_device()->StartRecording());
// Wait until audio has restarted and a new sequence of audio callbacks
// becomes active.
// TODO(henrika): is it possible to verify that the internal state transition
// is Stop->Init->Start?
ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock));
mock.ResetCallbackCounters();
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _,
false, _))
.Times(AtLeast(kNumCallbacks));
event()->Wait(kTestTimeOutInMilliseconds);
EXPECT_TRUE(audio_device()->Recording());
// Stop recording and the audio thread after successful internal restart.
StopRecording();
}
#endif // #ifdef WEBRTC_WIN
// Start playout and verify that the native audio layer starts asking for real // Start playout and verify that the native audio layer starts asking for real
// audio samples to play out using the NeedMorePlayData() callback. // audio samples to play out using the NeedMorePlayData() callback.
// 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
@ -866,6 +1005,35 @@ TEST_P(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) {
PRINT("\n"); PRINT("\n");
} }
// Runs audio in full duplex until user hits Enter. Intended as a manual test
// to ensure that the audio quality is good and that real device switches works
// as intended.
TEST_P(AudioDeviceTest,
DISABLED_RunPlayoutAndRecordingInFullDuplexAndWaitForEnterKey) {
SKIP_TEST_IF_NOT(requirements_satisfied());
if (audio_layer() != AudioDeviceModule::kWindowsCoreAudio2) {
return;
}
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
FifoAudioStream audio_stream;
mock.HandleCallbacks(&audio_stream);
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
EXPECT_EQ(0, audio_device()->SetStereoPlayout(true));
EXPECT_EQ(0, audio_device()->SetStereoRecording(true));
// Ensure that the sample rate for both directions are identical so that we
// always can listen to our own voice. Will lead to rate conversion (and
// higher latency) if the native sample rate is not 48kHz.
EXPECT_EQ(0, audio_device()->SetPlayoutSampleRate(48000));
EXPECT_EQ(0, audio_device()->SetRecordingSampleRate(48000));
StartPlayout();
StartRecording();
do {
PRINT("Loopback audio is active at 48kHz. Press Enter to stop.\n");
} while (getchar() != '\n');
StopRecording();
StopPlayout();
}
// Measures loopback latency and reports the min, max and average values for // Measures loopback latency and reports the min, max and average values for
// a full duplex audio session. // a full duplex audio session.
// The latency is measured like so: // The latency is measured like so:

View File

@ -26,6 +26,7 @@ FineAudioBuffer::FineAudioBuffer(AudioDeviceBuffer* audio_device_buffer)
playout_channels_(audio_device_buffer->PlayoutChannels()), playout_channels_(audio_device_buffer->PlayoutChannels()),
record_channels_(audio_device_buffer->RecordingChannels()) { record_channels_(audio_device_buffer->RecordingChannels()) {
RTC_DCHECK(audio_device_buffer_); RTC_DCHECK(audio_device_buffer_);
RTC_DLOG(INFO) << __FUNCTION__;
if (IsReadyForPlayout()) { if (IsReadyForPlayout()) {
RTC_DLOG(INFO) << "playout_samples_per_channel_10ms: " RTC_DLOG(INFO) << "playout_samples_per_channel_10ms: "
<< playout_samples_per_channel_10ms_; << playout_samples_per_channel_10ms_;
@ -38,7 +39,9 @@ FineAudioBuffer::FineAudioBuffer(AudioDeviceBuffer* audio_device_buffer)
} }
} }
FineAudioBuffer::~FineAudioBuffer() {} FineAudioBuffer::~FineAudioBuffer() {
RTC_DLOG(INFO) << __FUNCTION__;
}
void FineAudioBuffer::ResetPlayout() { void FineAudioBuffer::ResetPlayout() {
playout_buffer_.Clear(); playout_buffer_.Clear();

View File

@ -17,6 +17,8 @@
namespace webrtc { namespace webrtc {
class AudioDeviceModuleForTest;
class AudioDeviceModule : public rtc::RefCountInterface { class AudioDeviceModule : public rtc::RefCountInterface {
public: public:
// Deprecated. // Deprecated.
@ -46,9 +48,13 @@ class AudioDeviceModule : public rtc::RefCountInterface {
enum ChannelType { kChannelLeft = 0, kChannelRight = 1, kChannelBoth = 2 }; enum ChannelType { kChannelLeft = 0, kChannelRight = 1, kChannelBoth = 2 };
public: public:
// Creates an ADM. // Creates a default ADM for usage in production code.
static rtc::scoped_refptr<AudioDeviceModule> Create( static rtc::scoped_refptr<AudioDeviceModule> Create(
const AudioLayer audio_layer); const AudioLayer audio_layer);
// Creates an ADM with support for extra test methods. Don't use this factory
// in production code.
static rtc::scoped_refptr<AudioDeviceModuleForTest> CreateForTest(
const AudioLayer audio_layer);
// TODO(bugs.webrtc.org/7306): deprecated (to be removed). // TODO(bugs.webrtc.org/7306): deprecated (to be removed).
static rtc::scoped_refptr<AudioDeviceModule> Create( static rtc::scoped_refptr<AudioDeviceModule> Create(
const int32_t id, const int32_t id,
@ -158,6 +164,20 @@ class AudioDeviceModule : public rtc::RefCountInterface {
~AudioDeviceModule() override {} ~AudioDeviceModule() override {}
}; };
// Extends the default ADM interface with some extra test methods.
// Intended for usage in tests only and requires a unique factory method.
class AudioDeviceModuleForTest : public AudioDeviceModule {
public:
// Triggers internal restart sequences of audio streaming. Can be used by
// tests to emulate events corresponding to e.g. removal of an active audio
// device or other actions which causes the stream to be disconnected.
virtual int RestartPlayoutInternally() = 0;
virtual int RestartRecordingInternally() = 0;
virtual int SetPlayoutSampleRate(uint32_t sample_rate) = 0;
virtual int SetRecordingSampleRate(uint32_t sample_rate) = 0;
};
} // namespace webrtc } // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_H_ #endif // MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_H_

View File

@ -24,6 +24,12 @@ namespace webrtc {
rtc::scoped_refptr<AudioDeviceModule> rtc::scoped_refptr<AudioDeviceModule>
CreateWindowsCoreAudioAudioDeviceModule() { CreateWindowsCoreAudioAudioDeviceModule() {
RTC_DLOG(INFO) << __FUNCTION__;
return CreateWindowsCoreAudioAudioDeviceModuleForTest();
}
rtc::scoped_refptr<AudioDeviceModuleForTest>
CreateWindowsCoreAudioAudioDeviceModuleForTest() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
// Returns NULL if Core Audio is not supported or if COM has not been // Returns NULL if Core Audio is not supported or if COM has not been
// initialized correctly using webrtc_win::ScopedCOMInitializer. // initialized correctly using webrtc_win::ScopedCOMInitializer.

View File

@ -35,6 +35,9 @@ namespace webrtc {
// //
rtc::scoped_refptr<AudioDeviceModule> CreateWindowsCoreAudioAudioDeviceModule(); rtc::scoped_refptr<AudioDeviceModule> CreateWindowsCoreAudioAudioDeviceModule();
rtc::scoped_refptr<AudioDeviceModuleForTest>
CreateWindowsCoreAudioAudioDeviceModuleForTest();
} // namespace webrtc } // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_FACTORY_H_ #endif // MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_FACTORY_H_

View File

@ -24,6 +24,20 @@ namespace webrtc {
namespace webrtc_win { namespace webrtc_win {
namespace { namespace {
#define RETURN_IF_OUTPUT_RESTARTS(...) \
do { \
if (output_->Restarting()) { \
return __VA_ARGS__; \
} \
} while (0)
#define RETURN_IF_INPUT_RESTARTS(...) \
do { \
if (input_->Restarting()) { \
return __VA_ARGS__; \
} \
} while (0)
// This class combines a generic instance of an AudioInput and a generic // This class combines a generic instance of an AudioInput and a generic
// instance of an AudioOutput to create an AudioDeviceModule. This is mostly // 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 // done by delegating to the audio input/output with some glue code. This class
@ -34,7 +48,7 @@ namespace {
// i.e., all public methods must also be called on the same thread. A 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. // checker will RTC_DCHECK if any method is called on an invalid thread.
// TODO(henrika): is thread checking needed in AudioInput and AudioOutput? // TODO(henrika): is thread checking needed in AudioInput and AudioOutput?
class WindowsAudioDeviceModule : public AudioDeviceModule { class WindowsAudioDeviceModule : public AudioDeviceModuleForTest {
public: public:
enum class InitStatus { enum class InitStatus {
OK = 0, OK = 0,
@ -81,6 +95,8 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
int32_t Init() override { int32_t Init() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
RETURN_IF_INPUT_RESTARTS(0);
if (initialized_) { if (initialized_) {
return 0; return 0;
} }
@ -106,6 +122,8 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
int32_t Terminate() override { int32_t Terminate() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
RETURN_IF_INPUT_RESTARTS(0);
if (!initialized_) if (!initialized_)
return 0; return 0;
int32_t err = input_->Terminate(); int32_t err = input_->Terminate();
@ -123,12 +141,14 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
int16_t PlayoutDevices() override { int16_t PlayoutDevices() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
return output_->NumDevices(); return output_->NumDevices();
} }
int16_t RecordingDevices() override { int16_t RecordingDevices() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_INPUT_RESTARTS(0);
return input_->NumDevices(); return input_->NumDevices();
} }
@ -137,6 +157,7 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
char guid[kAdmMaxGuidSize]) override { char guid[kAdmMaxGuidSize]) override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
std::string name_str, guid_str; std::string name_str, guid_str;
int ret = -1; int ret = -1;
if (guid != nullptr) { if (guid != nullptr) {
@ -153,6 +174,7 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
char guid[kAdmMaxGuidSize]) override { char guid[kAdmMaxGuidSize]) override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_INPUT_RESTARTS(0);
std::string name_str, guid_str; std::string name_str, guid_str;
int ret = -1; int ret = -1;
if (guid != nullptr) { if (guid != nullptr) {
@ -168,6 +190,7 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
int32_t SetPlayoutDevice(uint16_t index) override { int32_t SetPlayoutDevice(uint16_t index) override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
return output_->SetDevice(index); return output_->SetDevice(index);
} }
@ -175,6 +198,7 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
AudioDeviceModule::WindowsDeviceType device) override { AudioDeviceModule::WindowsDeviceType device) override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
return output_->SetDevice(device); return output_->SetDevice(device);
} }
int32_t SetRecordingDevice(uint16_t index) override { int32_t SetRecordingDevice(uint16_t index) override {
@ -200,12 +224,14 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
int32_t InitPlayout() override { int32_t InitPlayout() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
return output_->InitPlayout(); return output_->InitPlayout();
} }
bool PlayoutIsInitialized() const override { bool PlayoutIsInitialized() const override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(true);
return output_->PlayoutIsInitialized(); return output_->PlayoutIsInitialized();
} }
@ -219,47 +245,55 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
int32_t InitRecording() override { int32_t InitRecording() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_INPUT_RESTARTS(0);
return input_->InitRecording(); return input_->InitRecording();
} }
bool RecordingIsInitialized() const override { bool RecordingIsInitialized() const override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_INPUT_RESTARTS(true);
return input_->RecordingIsInitialized(); return input_->RecordingIsInitialized();
} }
int32_t StartPlayout() override { int32_t StartPlayout() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
return output_->StartPlayout(); return output_->StartPlayout();
} }
int32_t StopPlayout() override { int32_t StopPlayout() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(-1);
return output_->StopPlayout(); return output_->StopPlayout();
} }
bool Playing() const override { bool Playing() const override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(true);
return output_->Playing(); return output_->Playing();
} }
int32_t StartRecording() override { int32_t StartRecording() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_INPUT_RESTARTS(0);
return input_->StartRecording(); return input_->StartRecording();
} }
int32_t StopRecording() override { int32_t StopRecording() override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_INPUT_RESTARTS(-1);
return input_->StopRecording(); return input_->StopRecording();
} }
bool Recording() const override { bool Recording() const override {
RTC_LOG(INFO) << __FUNCTION__; RTC_LOG(INFO) << __FUNCTION__;
RETURN_IF_INPUT_RESTARTS(true);
return input_->Recording(); return input_->Recording();
} }
@ -388,6 +422,31 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
return 0; return 0;
} }
int RestartPlayoutInternally() override {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
RETURN_IF_OUTPUT_RESTARTS(0);
return output_->RestartPlayout();
}
int RestartRecordingInternally() override {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
return input_->RestartRecording();
}
int SetPlayoutSampleRate(uint32_t sample_rate) override {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
return output_->SetSampleRate(sample_rate);
}
int SetRecordingSampleRate(uint32_t sample_rate) override {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
return input_->SetSampleRate(sample_rate);
}
private: private:
// Ensures that the class is used on the same thread as it is constructed // Ensures that the class is used on the same thread as it is constructed
// and destroyed on. // and destroyed on.
@ -410,7 +469,7 @@ class WindowsAudioDeviceModule : public AudioDeviceModule {
} // namespace } // namespace
rtc::scoped_refptr<AudioDeviceModule> rtc::scoped_refptr<AudioDeviceModuleForTest>
CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput( CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput(
std::unique_ptr<AudioInput> audio_input, std::unique_ptr<AudioInput> audio_input,
std::unique_ptr<AudioOutput> audio_output) { std::unique_ptr<AudioOutput> audio_output) {

View File

@ -42,6 +42,9 @@ class AudioInput {
virtual int StopRecording() = 0; virtual int StopRecording() = 0;
virtual bool Recording() = 0; virtual bool Recording() = 0;
virtual int VolumeIsAvailable(bool* available) = 0; virtual int VolumeIsAvailable(bool* available) = 0;
virtual int RestartRecording() = 0;
virtual bool Restarting() const = 0;
virtual int SetSampleRate(uint32_t sample_rate) = 0;
}; };
// This interface represents the main output-related parts of the complete // This interface represents the main output-related parts of the complete
@ -63,11 +66,14 @@ class AudioOutput {
virtual int StopPlayout() = 0; virtual int StopPlayout() = 0;
virtual bool Playing() = 0; virtual bool Playing() = 0;
virtual int VolumeIsAvailable(bool* available) = 0; virtual int VolumeIsAvailable(bool* available) = 0;
virtual int RestartPlayout() = 0;
virtual bool Restarting() const = 0;
virtual int SetSampleRate(uint32_t sample_rate) = 0;
}; };
// Combines an AudioInput and an AudioOutput implementation to build an // Combines an AudioInput and an AudioOutput implementation to build an
// AudioDeviceModule. Hides most parts of the full ADM interface. // AudioDeviceModule. Hides most parts of the full ADM interface.
rtc::scoped_refptr<AudioDeviceModule> rtc::scoped_refptr<AudioDeviceModuleForTest>
CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput( CreateWindowsCoreAudioAudioDeviceModuleFromInputAndOutput(
std::unique_ptr<AudioInput> audio_input, std::unique_ptr<AudioInput> audio_input,
std::unique_ptr<AudioOutput> audio_output); std::unique_ptr<AudioOutput> audio_output);

View File

@ -9,6 +9,7 @@
*/ */
#include "modules/audio_device/win/core_audio_base_win.h" #include "modules/audio_device/win/core_audio_base_win.h"
#include "modules/audio_device/audio_device_buffer.h"
#include <string> #include <string>
@ -17,6 +18,7 @@
#include "rtc_base/checks.h" #include "rtc_base/checks.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/timeutils.h"
#include "rtc_base/win/windows_version.h" #include "rtc_base/win/windows_version.h"
using Microsoft::WRL::ComPtr; using Microsoft::WRL::ComPtr;
@ -25,10 +27,22 @@ namespace webrtc {
namespace webrtc_win { namespace webrtc_win {
namespace { namespace {
// Even if the device supports low latency and even if IAudioClient3 can be
// used (requires Win10 or higher), we currently disable any attempts to
// initialize the client for low-latency.
// TODO(henrika): more research is needed before we can enable low-latency.
const bool kEnableLowLatencyIfSupported = false;
// Each unit of reference time is 100 nanoseconds, hence |kReftimesPerSec|
// corresponds to one second.
// TODO(henrika): possibly add usage in Init().
// const REFERENCE_TIME kReferenceTimesPerSecond = 10000000;
enum DefaultDeviceType { enum DefaultDeviceType {
kDefault, kUndefined = -1,
kDefaultCommunications, kDefault = 0,
kDefaultDeviceTypeMaxCount, kDefaultCommunications = 1,
kDefaultDeviceTypeMaxCount = kDefaultCommunications + 1,
}; };
const char* DirectionToString(CoreAudioBase::Direction direction) { const char* DirectionToString(CoreAudioBase::Direction direction) {
@ -42,35 +56,119 @@ const char* DirectionToString(CoreAudioBase::Direction direction) {
} }
} }
const char* SessionStateToString(AudioSessionState state) {
switch (state) {
case AudioSessionStateActive:
return "Active";
case AudioSessionStateInactive:
return "Inactive";
case AudioSessionStateExpired:
return "Expired";
default:
return "Invalid";
}
}
const char* SessionDisconnectReasonToString(
AudioSessionDisconnectReason reason) {
switch (reason) {
case DisconnectReasonDeviceRemoval:
return "DeviceRemoval";
case DisconnectReasonServerShutdown:
return "ServerShutdown";
case DisconnectReasonFormatChanged:
return "FormatChanged";
case DisconnectReasonSessionLogoff:
return "SessionLogoff";
case DisconnectReasonSessionDisconnected:
return "Disconnected";
case DisconnectReasonExclusiveModeOverride:
return "ExclusiveModeOverride";
default:
return "Invalid";
}
}
void Run(void* obj) { void Run(void* obj) {
RTC_DCHECK(obj); RTC_DCHECK(obj);
reinterpret_cast<CoreAudioBase*>(obj)->ThreadRun(); reinterpret_cast<CoreAudioBase*>(obj)->ThreadRun();
} }
// Returns true if the selected audio device supports low latency, i.e, if it
// is possible to initialize the engine using periods less than the default
// period (10ms).
bool IsLowLatencySupported(IAudioClient3* client3,
const WAVEFORMATEXTENSIBLE* format,
uint32_t* min_period_in_frames) {
RTC_DLOG(INFO) << __FUNCTION__;
// Get the range of periodicities supported by the engine for the specified
// stream format.
uint32_t default_period = 0;
uint32_t fundamental_period = 0;
uint32_t min_period = 0;
uint32_t max_period = 0;
if (FAILED(core_audio_utility::GetSharedModeEnginePeriod(
client3, format, &default_period, &fundamental_period, &min_period,
&max_period))) {
return false;
}
// Low latency is supported if the shortest allowed period is less than the
// default engine period.
// TODO(henrika): verify that this assumption is correct.
const bool low_latency = min_period < default_period;
RTC_LOG(INFO) << "low_latency: " << low_latency;
*min_period_in_frames = low_latency ? min_period : 0;
return low_latency;
}
} // namespace } // namespace
CoreAudioBase::CoreAudioBase(Direction direction, OnDataCallback callback) CoreAudioBase::CoreAudioBase(Direction direction,
: direction_(direction), on_data_callback_(callback), format_() { OnDataCallback data_callback,
OnErrorCallback error_callback)
: format_(),
direction_(direction),
on_data_callback_(data_callback),
on_error_callback_(error_callback),
device_index_(kUndefined),
is_restarting_(false) {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction) << "]"; RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction) << "]";
RTC_DLOG(INFO) << "Windows version: " << rtc::rtc_win::GetVersion();
// Create the event which the audio engine will signal each time a buffer // Create the event which the audio engine will signal each time a buffer
// becomes ready to be processed by the client. // becomes ready to be processed by the client.
audio_samples_event_.Set(CreateEvent(nullptr, false, false, nullptr)); audio_samples_event_.Set(CreateEvent(nullptr, false, false, nullptr));
RTC_DCHECK(audio_samples_event_.IsValid()); RTC_DCHECK(audio_samples_event_.IsValid());
// Event to be be set in Stop() when rendering/capturing shall stop. // Event to be set in Stop() when rendering/capturing shall stop.
stop_event_.Set(CreateEvent(nullptr, false, false, nullptr)); stop_event_.Set(CreateEvent(nullptr, false, false, nullptr));
RTC_DCHECK(stop_event_.IsValid()); RTC_DCHECK(stop_event_.IsValid());
// Event to be set when it has been detected that an active device has been
// invalidated or the stream format has changed.
restart_event_.Set(CreateEvent(nullptr, false, false, nullptr));
RTC_DCHECK(restart_event_.IsValid());
} }
CoreAudioBase::~CoreAudioBase() { CoreAudioBase::~CoreAudioBase() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_EQ(ref_count_, 1);
} }
EDataFlow CoreAudioBase::GetDataFlow() const { EDataFlow CoreAudioBase::GetDataFlow() const {
return direction_ == CoreAudioBase::Direction::kOutput ? eRender : eCapture; return direction_ == CoreAudioBase::Direction::kOutput ? eRender : eCapture;
} }
bool CoreAudioBase::IsRestarting() const {
return is_restarting_;
}
int64_t CoreAudioBase::TimeSinceStart() const {
return rtc::TimeSince(start_time_);
}
int CoreAudioBase::NumberOfActiveDevices() const { int CoreAudioBase::NumberOfActiveDevices() const {
return core_audio_utility::NumberOfActiveDevices(GetDataFlow()); return core_audio_utility::NumberOfActiveDevices(GetDataFlow());
} }
@ -80,6 +178,21 @@ int CoreAudioBase::NumberOfEnumeratedDevices() const {
return num_active > 0 ? num_active + kDefaultDeviceTypeMaxCount : 0; return num_active > 0 ? num_active + kDefaultDeviceTypeMaxCount : 0;
} }
void CoreAudioBase::ReleaseCOMObjects() {
RTC_DLOG(INFO) << __FUNCTION__;
// ComPtr::Reset() sets the ComPtr to nullptr releasing any previous
// reference.
if (audio_client_) {
audio_client_.Reset();
}
if (audio_clock_.Get()) {
audio_clock_.Reset();
}
if (audio_session_control_.Get()) {
audio_session_control_.Reset();
}
}
bool CoreAudioBase::IsDefaultDevice(int index) const { bool CoreAudioBase::IsDefaultDevice(int index) const {
return index == kDefault; return index == kDefault;
} }
@ -138,6 +251,21 @@ std::string CoreAudioBase::GetDeviceID(int index) const {
return device_id; return device_id;
} }
int CoreAudioBase::SetDevice(int index) {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]";
if (initialized_) {
return -1;
}
std::string device_id = GetDeviceID(index);
RTC_DLOG(INFO) << "index=" << index << " => device_id: " << device_id;
device_index_ = index;
device_id_ = device_id;
return device_id_.empty() ? -1 : 0;
}
int CoreAudioBase::DeviceName(int index, int CoreAudioBase::DeviceName(int index,
std::string* name, std::string* name,
std::string* guid) const { std::string* guid) const {
@ -170,11 +298,11 @@ bool CoreAudioBase::Init() {
<< "]"; << "]";
RTC_DCHECK(!device_id_.empty()); RTC_DCHECK(!device_id_.empty());
RTC_DCHECK(audio_device_buffer_); RTC_DCHECK(audio_device_buffer_);
RTC_DCHECK(!audio_client_.Get()); RTC_DCHECK(!audio_client_);
RTC_DCHECK(!audio_session_control_.Get());
// Use an existing |device_id_| and set parameters which are required to // 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_|. // 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_; std::string device_id = device_id_;
ERole role = eConsole; ERole role = eConsole;
if (IsDefaultDevice(device_id)) { if (IsDefaultDevice(device_id)) {
@ -183,20 +311,51 @@ bool CoreAudioBase::Init() {
} else if (IsDefaultCommunicationsDevice(device_id)) { } else if (IsDefaultCommunicationsDevice(device_id)) {
device_id = AudioDeviceName::kDefaultCommunicationsDeviceId; device_id = AudioDeviceName::kDefaultCommunicationsDeviceId;
role = eCommunications; role = eCommunications;
} else {
RTC_DLOG(LS_WARNING) << "Not using a default device";
} }
// Create an IAudioClient interface which enables us to create and initialize // Create an IAudioClient interface which enables us to create and initialize
// an audio stream between an audio application and the audio engine. // an audio stream between an audio application and the audio engine.
ComPtr<IAudioClient> audio_client = ComPtr<IAudioClient> audio_client;
core_audio_utility::CreateClient(device_id, GetDataFlow(), role); if (core_audio_utility::GetAudioClientVersion() == 3) {
if (!audio_client.Get()) { RTC_DLOG(INFO) << "Using IAudioClient3";
audio_client =
core_audio_utility::CreateClient3(device_id, GetDataFlow(), role);
} else if (core_audio_utility::GetAudioClientVersion() == 2) {
RTC_DLOG(INFO) << "Using IAudioClient2";
audio_client =
core_audio_utility::CreateClient2(device_id, GetDataFlow(), role);
} else {
RTC_DLOG(INFO) << "Using IAudioClient";
audio_client =
core_audio_utility::CreateClient(device_id, GetDataFlow(), role);
}
if (!audio_client) {
return false; return false;
} }
// Retrieve preferred audio input or output parameters for the given client. // Set extra client properties before initialization if the audio client
// supports it.
// TODO(henrika): evaluate effect(s) of making these changes. Also, perhaps
// these types of settings belongs to the client and not the utility parts.
if (core_audio_utility::GetAudioClientVersion() >= 2) {
if (FAILED(core_audio_utility::SetClientProperties(
static_cast<IAudioClient2*>(audio_client.Get())))) {
return false;
}
}
// Retrieve preferred audio input or output parameters for the given client
// and the specified client properties. Override the preferred rate if sample
// rate has been defined by the user. Rate conversion will be performed by
// the audio engine to match the client if needed.
AudioParameters params; AudioParameters params;
if (FAILED(core_audio_utility::GetPreferredAudioParameters(audio_client.Get(), HRESULT res = sample_rate_ ? core_audio_utility::GetPreferredAudioParameters(
&params))) { audio_client.Get(), &params, *sample_rate_)
: core_audio_utility::GetPreferredAudioParameters(
audio_client.Get(), &params);
if (FAILED(res)) {
return false; return false;
} }
@ -219,24 +378,66 @@ bool CoreAudioBase::Init() {
format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
RTC_DLOG(INFO) << core_audio_utility::WaveFormatExToString(&format_); RTC_DLOG(INFO) << core_audio_utility::WaveFormatExToString(&format_);
// Verify that the format is supported. // Verify that the format is supported but exclude the test if the default
if (!core_audio_utility::IsFormatSupported( // sample rate has been overridden. If so, the WASAPI audio engine will do
audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &format_)) { // any necessary conversions between the client format we have given it and
return false; // the playback mix format or recording split format.
if (!sample_rate_) {
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 // Check if low-latency is supported and use special initialization if it is.
// mode using event-driven buffer handling. // Low-latency initialization requires these things:
if (FAILED(core_audio_utility::SharedModeInitialize( // - IAudioClient3 (>= Win10)
audio_client.Get(), &format_, audio_samples_event_, // - HDAudio driver
&endpoint_buffer_size_frames_))) { // - kEnableLowLatencyIfSupported changed from false (default) to true.
return false; // TODO(henrika): IsLowLatencySupported() returns AUDCLNT_E_UNSUPPORTED_FORMAT
// when |sample_rate_.has_value()| returns true if rate conversion is
// actually required (i.e., client asks for other than the default rate).
bool low_latency_support = false;
uint32_t min_period_in_frames = 0;
if (kEnableLowLatencyIfSupported &&
core_audio_utility::GetAudioClientVersion() >= 3) {
low_latency_support =
IsLowLatencySupported(static_cast<IAudioClient3*>(audio_client.Get()),
&format_, &min_period_in_frames);
}
if (low_latency_support) {
RTC_DCHECK_GE(core_audio_utility::GetAudioClientVersion(), 3);
// Use IAudioClient3::InitializeSharedAudioStream() API to initialize a
// low-latency event-driven client. Request the smallest possible
// periodicity.
// TODO(henrika): evaluate this scheme in terms of CPU etc.
if (FAILED(core_audio_utility::SharedModeInitializeLowLatency(
static_cast<IAudioClient3*>(audio_client.Get()), &format_,
audio_samples_event_, min_period_in_frames,
sample_rate_.has_value(), &endpoint_buffer_size_frames_))) {
return false;
}
} else {
// Initialize the audio stream between the client and the device in shared
// mode using event-driven buffer handling. Also, using 0 as requested
// buffer size results in a default (minimum) endpoint buffer size.
// TODO(henrika): possibly increase |requested_buffer_size| to add
// robustness.
const REFERENCE_TIME requested_buffer_size = 0;
if (FAILED(core_audio_utility::SharedModeInitialize(
audio_client.Get(), &format_, audio_samples_event_,
requested_buffer_size, sample_rate_.has_value(),
&endpoint_buffer_size_frames_))) {
return false;
}
} }
// Check device period and the preferred buffer size and log a warning if // 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 // WebRTC's buffer size is not an even divisor of the preferred buffer size
// in Core Audio. // in Core Audio.
// TODO(henrik): sort out if a non-perfect match really is an issue. // TODO(henrika): sort out if a non-perfect match really is an issue.
// TODO(henrika): compare with IAudioClient3::GetSharedModeEnginePeriod().
REFERENCE_TIME device_period; REFERENCE_TIME device_period;
if (FAILED(core_audio_utility::GetDevicePeriod( if (FAILED(core_audio_utility::GetDevicePeriod(
audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &device_period))) { audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &device_period))) {
@ -256,8 +457,35 @@ bool CoreAudioBase::Init() {
<< preferred_frames_per_buffer; << preferred_frames_per_buffer;
} }
// Store valid COM interfaces. // Create an AudioSessionControl interface given the initialized client.
audio_client_ = audio_client; // The IAudioControl interface enables a client to configure the control
// parameters for an audio session and to monitor events in the session.
ComPtr<IAudioSessionControl> audio_session_control =
core_audio_utility::CreateAudioSessionControl(audio_client.Get());
if (!audio_session_control.Get()) {
return false;
}
// The Sndvol program displays volume and mute controls for sessions that
// are in the active and inactive states.
AudioSessionState state;
if (FAILED(audio_session_control->GetState(&state))) {
return false;
}
RTC_DLOG(INFO) << "audio session state: " << SessionStateToString(state);
RTC_DCHECK_EQ(state, AudioSessionStateInactive);
// Register the client to receive notifications of session events, including
// changes in the stream state.
if (FAILED(audio_session_control->RegisterAudioSessionNotification(this))) {
return false;
}
// Store valid COM interface.
if (audio_client) {
audio_client_ = audio_client;
}
audio_session_control_ = audio_session_control;
return true; return true;
} }
@ -265,45 +493,65 @@ bool CoreAudioBase::Init() {
bool CoreAudioBase::Start() { bool CoreAudioBase::Start() {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction()) RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]"; << "]";
if (IsRestarting()) {
audio_thread_ = absl::make_unique<rtc::PlatformThread>( // Audio thread should be alive during internal restart since the restart
Run, this, IsInput() ? "wasapi_capture_thread" : "wasapi_render_thread", // callback is triggered on that thread and it also makes the restart
rtc::kRealtimePriority); // sequence less complex.
audio_thread_->Start(); RTC_DCHECK(audio_thread_);
if (!audio_thread_->IsRunning()) { }
StopThread();
RTC_LOG(LS_ERROR) << "Failed to start audio thread"; // Start an audio thread but only if one does not already exist (which is the
return false; // case during restart).
if (!audio_thread_) {
audio_thread_ = absl::make_unique<rtc::PlatformThread>(
Run, this, IsInput() ? "wasapi_capture_thread" : "wasapi_render_thread",
rtc::kRealtimePriority);
RTC_DCHECK(audio_thread_);
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()
<< " and id: " << audio_thread_->GetThreadRef();
} }
RTC_DLOG(INFO) << "Started thread with name: " << audio_thread_->name();
// Start streaming data between the endpoint buffer and the audio engine. // Start streaming data between the endpoint buffer and the audio engine.
_com_error error = audio_client_->Start(); _com_error error = audio_client_->Start();
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
StopThread(); StopThread();
RTC_LOG(LS_ERROR) << "IAudioClient::Start failed: " RTC_LOG(LS_ERROR) << "IAudioClient::Start failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
} }
start_time_ = rtc::TimeMillis();
num_data_callbacks_ = 0;
return true; return true;
} }
bool CoreAudioBase::Stop() { bool CoreAudioBase::Stop() {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction()) RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]"; << "]";
RTC_DLOG(INFO) << "total activity time: " << TimeSinceStart();
// Stop streaming and the internal audio thread. // Stop audio streaming.
_com_error error = audio_client_->Stop(); _com_error error = audio_client_->Stop();
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::Stop failed: " RTC_LOG(LS_ERROR) << "IAudioClient::Stop failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
} }
StopThread(); // Stop and destroy the audio thread but only when a restart attempt is not
// ongoing.
if (!IsRestarting()) {
StopThread();
}
// Flush all pending data and reset the audio clock stream position to 0. // Flush all pending data and reset the audio clock stream position to 0.
error = audio_client_->Reset(); error = audio_client_->Reset();
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::Reset failed: " RTC_LOG(LS_ERROR) << "IAudioClient::Reset failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
} }
@ -318,6 +566,30 @@ bool CoreAudioBase::Stop() {
RTC_DCHECK_EQ(0u, num_queued_frames); RTC_DCHECK_EQ(0u, num_queued_frames);
} }
// Delete the previous registration by the client to receive notifications
// about audio session events.
RTC_DLOG(INFO) << "audio session state: "
<< SessionStateToString(GetAudioSessionState());
error = audio_session_control_->UnregisterAudioSessionNotification(this);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR)
<< "IAudioSessionControl::UnregisterAudioSessionNotification failed: "
<< core_audio_utility::ErrorToString(error);
}
// To ensure that the restart process is as simple as possible, the audio
// thread is not destroyed during restart attempts triggered by internal
// error callbacks.
if (!IsRestarting()) {
thread_checker_audio_.DetachFromThread();
IsOutput() ? audio_device_buffer_->NativeAudioPlayoutInterrupted()
: audio_device_buffer_->NativeAudioRecordingInterrupted();
}
// Release all allocated COM interfaces to allow for a restart without
// intermediate destruction.
ReleaseCOMObjects();
return true; return true;
} }
@ -327,7 +599,7 @@ bool CoreAudioBase::IsVolumeControlAvailable(bool* available) const {
// as well but we use the audio client here to ensure that the initialized // as well but we use the audio client here to ensure that the initialized
// audio session is visible under group box labeled "Applications" in // audio session is visible under group box labeled "Applications" in
// Sndvol.exe. // Sndvol.exe.
if (!audio_client_.Get()) { if (!audio_client_) {
return false; return false;
} }
@ -353,8 +625,21 @@ bool CoreAudioBase::IsVolumeControlAvailable(bool* available) const {
return false; return false;
} }
// Internal test method which can be used in tests to emulate a restart signal.
// It simply sets the same event which is normally triggered by session and
// device notifications. Hence, the emulated restart sequence covers most parts
// of a real sequence expect the actual device switch.
bool CoreAudioBase::Restart() {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]";
is_restarting_ = true;
SetEvent(restart_event_.Get());
return true;
}
void CoreAudioBase::StopThread() { void CoreAudioBase::StopThread() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK(!IsRestarting());
if (audio_thread_) { if (audio_thread_) {
if (audio_thread_->IsRunning()) { if (audio_thread_->IsRunning()) {
RTC_DLOG(INFO) << "Sets stop_event..."; RTC_DLOG(INFO) << "Sets stop_event...";
@ -367,15 +652,164 @@ void CoreAudioBase::StopThread() {
// Ensure that we don't quit the main thread loop immediately next // Ensure that we don't quit the main thread loop immediately next
// time Start() is called. // time Start() is called.
ResetEvent(stop_event_.Get()); ResetEvent(stop_event_.Get());
ResetEvent(restart_event_.Get());
} }
} }
bool CoreAudioBase::HandleRestartEvent() {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]";
RTC_DCHECK_RUN_ON(&thread_checker_audio_);
RTC_DCHECK(audio_thread_);
RTC_DCHECK(IsRestarting());
// Let each client (input and/or output) take care of its own restart
// sequence since each side might need unique actions.
// TODO(henrika): revisit and investigate if one common base implementation
// is possible
bool restart_ok = on_error_callback_(ErrorType::kStreamDisconnected);
is_restarting_ = false;
return restart_ok;
}
bool CoreAudioBase::SwitchDeviceIfNeeded() {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]";
RTC_DCHECK_RUN_ON(&thread_checker_audio_);
RTC_DCHECK(IsRestarting());
RTC_DLOG(INFO) << "device_index=" << device_index_
<< " => device_id: " << device_id_;
// Ensure that at least one device exists and can be utilized. The most
// probable cause for ending up here is that a device has been removed.
if (core_audio_utility::NumberOfActiveDevices(IsInput() ? eCapture
: eRender) < 1) {
RTC_DLOG(LS_ERROR) << "All devices are disabled or removed";
return false;
}
// Get the unique device ID for the index which is currently used. It seems
// safe to assume that if the ID is the same as the existing device ID, then
// the device configuration is the same as before.
std::string device_id = GetDeviceID(device_index_);
if (device_id != device_id_) {
RTC_LOG(LS_WARNING)
<< "Device configuration has changed => changing device selection...";
// TODO(henrika): depending on the current state and how we got here, we
// must select a new device here.
if (SetDevice(kDefault) == -1) {
RTC_LOG(LS_WARNING) << "Failed to set new audio device";
return false;
}
} else {
RTC_LOG(INFO)
<< "Device configuration has not changed => keeping selected device";
}
return true;
}
AudioSessionState CoreAudioBase::GetAudioSessionState() const {
AudioSessionState state = AudioSessionStateInactive;
RTC_DCHECK(audio_session_control_.Get());
_com_error error = audio_session_control_->GetState(&state);
if (FAILED(error.Error())) {
RTC_DLOG(LS_ERROR) << "IAudioSessionControl::GetState failed: "
<< core_audio_utility::ErrorToString(error);
}
return state;
}
// TODO(henrika): only used for debugging purposes currently.
ULONG CoreAudioBase::AddRef() {
ULONG new_ref = InterlockedIncrement(&ref_count_);
// RTC_DLOG(INFO) << "__AddRef => " << new_ref;
return new_ref;
}
// TODO(henrika): does not call delete this.
ULONG CoreAudioBase::Release() {
ULONG new_ref = InterlockedDecrement(&ref_count_);
// RTC_DLOG(INFO) << "__Release => " << new_ref;
return new_ref;
}
// TODO(henrika): can probably be replaced by "return S_OK" only.
HRESULT CoreAudioBase::QueryInterface(REFIID iid, void** object) {
if (object == nullptr) {
return E_POINTER;
}
if (iid == IID_IUnknown || iid == __uuidof(IAudioSessionEvents)) {
*object = static_cast<IAudioSessionEvents*>(this);
return S_OK;
};
*object = nullptr;
return E_NOINTERFACE;
}
// IAudioSessionEvents::OnStateChanged.
HRESULT CoreAudioBase::OnStateChanged(AudioSessionState new_state) {
RTC_DLOG(INFO) << "___" << __FUNCTION__ << "["
<< DirectionToString(direction())
<< "] new_state: " << SessionStateToString(new_state);
return S_OK;
}
// When a session is disconnected because of a device removal or format change
// event, we want to inform the audio thread about the lost audio session and
// trigger an attempt to restart audio using a new (default) device.
HRESULT CoreAudioBase::OnSessionDisconnected(
AudioSessionDisconnectReason disconnect_reason) {
RTC_DLOG(INFO) << "___" << __FUNCTION__ << "["
<< DirectionToString(direction()) << "] reason: "
<< SessionDisconnectReasonToString(disconnect_reason);
if (disconnect_reason == DisconnectReasonDeviceRemoval ||
disconnect_reason == DisconnectReasonFormatChanged) {
is_restarting_ = true;
SetEvent(restart_event_.Get());
}
return S_OK;
}
// IAudioSessionEvents::OnDisplayNameChanged
HRESULT CoreAudioBase::OnDisplayNameChanged(LPCWSTR new_display_name,
LPCGUID event_context) {
return S_OK;
}
// IAudioSessionEvents::OnIconPathChanged
HRESULT CoreAudioBase::OnIconPathChanged(LPCWSTR new_icon_path,
LPCGUID event_context) {
return S_OK;
}
// IAudioSessionEvents::OnSimpleVolumeChanged
HRESULT CoreAudioBase::OnSimpleVolumeChanged(float new_simple_volume,
BOOL new_mute,
LPCGUID event_context) {
return S_OK;
}
// IAudioSessionEvents::OnChannelVolumeChanged
HRESULT CoreAudioBase::OnChannelVolumeChanged(DWORD channel_count,
float new_channel_volumes[],
DWORD changed_channel,
LPCGUID event_context) {
return S_OK;
}
// IAudioSessionEvents::OnGroupingParamChanged
HRESULT CoreAudioBase::OnGroupingParamChanged(LPCGUID new_grouping_param,
LPCGUID event_context) {
return S_OK;
}
void CoreAudioBase::ThreadRun() { void CoreAudioBase::ThreadRun() {
if (!core_audio_utility::IsMMCSSSupported()) { if (!core_audio_utility::IsMMCSSSupported()) {
RTC_LOG(LS_ERROR) << "MMCSS is not supported"; RTC_LOG(LS_ERROR) << "MMCSS is not supported";
return; return;
} }
RTC_DLOG(INFO) << "ThreadRun starts..."; RTC_DLOG(INFO) << "[" << DirectionToString(direction())
<< "] ThreadRun starts...";
// TODO(henrika): difference between "Pro Audio" and "Audio"? // TODO(henrika): difference between "Pro Audio" and "Audio"?
ScopedMMCSSRegistration mmcss_registration(L"Pro Audio"); ScopedMMCSSRegistration mmcss_registration(L"Pro Audio");
ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA); ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA);
@ -386,17 +820,19 @@ void CoreAudioBase::ThreadRun() {
bool streaming = true; bool streaming = true;
bool error = false; bool error = false;
HANDLE wait_array[] = {stop_event_.Get(), audio_samples_event_.Get()}; HANDLE wait_array[] = {stop_event_.Get(), restart_event_.Get(),
audio_samples_event_.Get()};
// The device frequency is the frequency generated by the hardware clock in // The device frequency is the frequency generated by the hardware clock in
// the audio device. The GetFrequency() method reports a constant frequency. // the audio device. The GetFrequency() method reports a constant frequency.
UINT64 device_frequency = 0; UINT64 device_frequency = 0;
if (audio_clock_.Get()) { _com_error result(S_FALSE);
if (audio_clock_) {
RTC_DCHECK(IsOutput()); RTC_DCHECK(IsOutput());
_com_error result = audio_clock_->GetFrequency(&device_frequency); result = audio_clock_->GetFrequency(&device_frequency);
if ((error = result.Error()) != S_OK) { if (FAILED(result.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClock::GetFrequency failed: " RTC_LOG(LS_ERROR) << "IAudioClock::GetFrequency failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(result);
} }
} }
@ -412,6 +848,10 @@ void CoreAudioBase::ThreadRun() {
streaming = false; streaming = false;
break; break;
case WAIT_OBJECT_0 + 1: case WAIT_OBJECT_0 + 1:
// |restart_event_| has been set.
error = !HandleRestartEvent();
break;
case WAIT_OBJECT_0 + 2:
// |audio_samples_event_| has been set. // |audio_samples_event_| has been set.
error = !on_data_callback_(device_frequency); error = !on_data_callback_(device_frequency);
break; break;
@ -422,17 +862,23 @@ void CoreAudioBase::ThreadRun() {
} }
if (streaming && error) { if (streaming && error) {
RTC_LOG(LS_ERROR) << "WASAPI streaming failed."; RTC_LOG(LS_ERROR) << "[" << DirectionToString(direction())
<< "] WASAPI streaming failed.";
// Stop audio streaming since something has gone wrong in our main thread // 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 // loop. Note that, we are still in a "started" state, hence a Stop() call
// is required to join the thread properly. // is required to join the thread properly.
audio_client_->Stop(); result = audio_client_->Stop();
if (FAILED(result.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::Stop failed: "
<< core_audio_utility::ErrorToString(result);
}
// TODO(henrika): notify clients that something has gone wrong and that // TODO(henrika): notify clients that something has gone wrong and that
// this stream should be destroyed instead of reused in the future. // this stream should be destroyed instead of reused in the future.
} }
RTC_DLOG(INFO) << "...ThreadRun stops"; RTC_DLOG(INFO) << "[" << DirectionToString(direction())
<< "] ...ThreadRun stops";
} }
} // namespace webrtc_win } // namespace webrtc_win

View File

@ -11,10 +11,12 @@
#ifndef MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_ #ifndef MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_
#define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_ #define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_BASE_WIN_H_
#include <atomic>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include "absl/types/optional.h"
#include "modules/audio_device/win/core_audio_utility_win.h" #include "modules/audio_device/win/core_audio_utility_win.h"
#include "rtc_base/platform_thread.h" #include "rtc_base/platform_thread.h"
#include "rtc_base/thread_checker.h" #include "rtc_base/thread_checker.h"
@ -29,30 +31,44 @@ namespace webrtc_win {
// Serves as base class for CoreAudioInput and CoreAudioOutput and supports // Serves as base class for CoreAudioInput and CoreAudioOutput and supports
// device handling and audio streaming where the direction (input or output) // device handling and audio streaming where the direction (input or output)
// is set at constructions by the parent. // is set at constructions by the parent.
class CoreAudioBase { // The IAudioSessionEvents interface provides notifications of session-related
// events such as changes in the volume level, display name, and session state.
// This class does not use the default ref-counting memory management method
// provided by IUnknown: calling CoreAudioBase::Release() will not delete the
// object. The client will receive notification from the session manager on
// a separate thread owned and controlled by the manager.
// TODO(henrika): investigate if CoreAudioBase should implement
// IMMNotificationClient as well (might improve support for device changes).
class CoreAudioBase : public IAudioSessionEvents {
public: public:
enum class Direction { enum class Direction {
kInput, kInput,
kOutput, kOutput,
}; };
// TODO(henrika): add more error types.
enum class ErrorType {
kStreamDisconnected,
};
template <typename T>
auto as_integer(T const value) -> typename std::underlying_type<T>::type {
return static_cast<typename std::underlying_type<T>::type>(value);
}
// Callback definition for notifications of new audio data. For input clients, // Callback definition for notifications of new audio data. For input clients,
// it means that "new audio data has now been captured", and for output // it means that "new audio data has now been captured", and for output
// clients, "the output layer now needs new audio data". // clients, "the output layer now needs new audio data".
typedef std::function<bool(uint64_t device_frequency)> OnDataCallback; typedef std::function<bool(uint64_t device_frequency)> OnDataCallback;
explicit CoreAudioBase(Direction direction, OnDataCallback callback); // Callback definition for notifications of run-time error messages. It can
~CoreAudioBase(); // be called e.g. when an active audio device is removed and an audio stream
// is disconnected (|error| is then set to kStreamDisconnected). Both input
std::string GetDeviceID(int index) const; // and output clients implements OnErrorCallback() and will trigger an
int DeviceName(int index, std::string* name, std::string* guid) const; // internal restart sequence for kStreamDisconnected.
// This method is currently always called on the audio thread.
bool Init(); // TODO(henrika): add support for more error types.
bool Start(); typedef std::function<bool(ErrorType error)> OnErrorCallback;
bool Stop();
bool IsVolumeControlAvailable(bool* available) const;
Direction direction() const { return direction_; }
void ThreadRun(); void ThreadRun();
@ -60,7 +76,33 @@ class CoreAudioBase {
CoreAudioBase& operator=(const CoreAudioBase&) = delete; CoreAudioBase& operator=(const CoreAudioBase&) = delete;
protected: protected:
// Returns number of active devices given the specified |direction_|. explicit CoreAudioBase(Direction direction,
OnDataCallback data_callback,
OnErrorCallback error_callback);
~CoreAudioBase();
std::string GetDeviceID(int index) const;
int SetDevice(int index);
int DeviceName(int index, std::string* name, std::string* guid) const;
// Checks if the current device ID is no longer in use (e.g. due to a
// disconnected stream), and if so, switches device to the default audio
// device. Called on the audio thread during restart attempts.
bool SwitchDeviceIfNeeded();
bool Init();
bool Start();
bool Stop();
bool IsVolumeControlAvailable(bool* available) const;
bool Restart();
Direction direction() const { return direction_; }
// Releases all allocated COM resources in the base class.
void ReleaseCOMObjects();
// Returns number of active devices given the specified |direction_| set
// by the parent (input or output).
int NumberOfActiveDevices() const; int NumberOfActiveDevices() const;
// Returns total number of enumerated audio devices which is the sum of all // Returns total number of enumerated audio devices which is the sum of all
@ -76,24 +118,79 @@ class CoreAudioBase {
bool IsDefaultDevice(const std::string& device_id) const; bool IsDefaultDevice(const std::string& device_id) const;
bool IsDefaultCommunicationsDevice(const std::string& device_id) const; bool IsDefaultCommunicationsDevice(const std::string& device_id) const;
EDataFlow GetDataFlow() const; EDataFlow GetDataFlow() const;
bool IsRestarting() const;
int64_t TimeSinceStart() const;
// TODO(henrika): is the existing thread checker in WindowsAudioDeviceModule
// sufficient? As is, we have one top-level protection and then a second
// level here. In addition, calls to Init(), Start() and Stop() are not
// included to allow for support of internal restart (where these methods are
// called on the audio thread).
rtc::ThreadChecker thread_checker_; rtc::ThreadChecker thread_checker_;
rtc::ThreadChecker thread_checker_audio_; rtc::ThreadChecker thread_checker_audio_;
const Direction direction_;
const OnDataCallback on_data_callback_;
AudioDeviceBuffer* audio_device_buffer_ = nullptr; AudioDeviceBuffer* audio_device_buffer_ = nullptr;
bool initialized_ = false; bool initialized_ = false;
std::string device_id_;
WAVEFORMATEXTENSIBLE format_ = {}; WAVEFORMATEXTENSIBLE format_ = {};
uint32_t endpoint_buffer_size_frames_ = 0; uint32_t endpoint_buffer_size_frames_ = 0;
Microsoft::WRL::ComPtr<IAudioClient> audio_client_;
Microsoft::WRL::ComPtr<IAudioClock> audio_clock_; Microsoft::WRL::ComPtr<IAudioClock> audio_clock_;
ScopedHandle audio_samples_event_; Microsoft::WRL::ComPtr<IAudioClient> audio_client_;
ScopedHandle stop_event_; bool is_active_ = false;
std::unique_ptr<rtc::PlatformThread> audio_thread_; int64_t num_data_callbacks_ = 0;
int latency_ms_ = 0;
absl::optional<uint32_t> sample_rate_;
private: private:
const Direction direction_;
const OnDataCallback on_data_callback_;
const OnErrorCallback on_error_callback_;
ScopedHandle audio_samples_event_;
ScopedHandle stop_event_;
ScopedHandle restart_event_;
int64_t start_time_ = 0;
std::string device_id_;
int device_index_;
// Used by the IAudioSessionEvents implementations. Currently only utilized
// for debugging purposes.
LONG ref_count_ = 1;
// Set when restart process starts and cleared when restart stops
// successfully. Accessed atomically.
std::atomic<bool> is_restarting_;
std::unique_ptr<rtc::PlatformThread> audio_thread_;
Microsoft::WRL::ComPtr<IAudioSessionControl> audio_session_control_;
void StopThread(); void StopThread();
AudioSessionState GetAudioSessionState() const;
// Called on the audio thread when a restart event has been set.
// It will then trigger calls to the installed error callbacks with error
// type set to kStreamDisconnected.
bool HandleRestartEvent();
// IUnknown (required by IAudioSessionEvents and IMMNotificationClient).
ULONG __stdcall AddRef() override;
ULONG __stdcall Release() override;
HRESULT __stdcall QueryInterface(REFIID iid, void** object) override;
// IAudioSessionEvents implementation.
// These methods are called on separate threads owned by the session manager.
// More than one thread can be involved depending on the type of callback
// and audio session.
HRESULT __stdcall OnStateChanged(AudioSessionState new_state) override;
HRESULT __stdcall OnSessionDisconnected(
AudioSessionDisconnectReason disconnect_reason) override;
HRESULT __stdcall OnDisplayNameChanged(LPCWSTR new_display_name,
LPCGUID event_context) override;
HRESULT __stdcall OnIconPathChanged(LPCWSTR new_icon_path,
LPCGUID event_context) override;
HRESULT __stdcall OnSimpleVolumeChanged(float new_simple_volume,
BOOL new_mute,
LPCGUID event_context) override;
HRESULT __stdcall OnChannelVolumeChanged(DWORD channel_count,
float new_channel_volumes[],
DWORD changed_channel,
LPCGUID event_context) override;
HRESULT __stdcall OnGroupingParamChanged(LPCGUID new_grouping_param,
LPCGUID event_context) override;
}; };
} // namespace webrtc_win } // namespace webrtc_win

View File

@ -22,9 +22,14 @@ using Microsoft::WRL::ComPtr;
namespace webrtc { namespace webrtc {
namespace webrtc_win { namespace webrtc_win {
enum AudioDeviceMessageType : uint32_t {
kMessageInputStreamDisconnected,
};
CoreAudioInput::CoreAudioInput() CoreAudioInput::CoreAudioInput()
: CoreAudioBase(CoreAudioBase::Direction::kInput, : CoreAudioBase(CoreAudioBase::Direction::kInput,
[this](uint64_t freq) { return OnDataCallback(freq); }) { [this](uint64_t freq) { return OnDataCallback(freq); },
[this](ErrorType err) { return OnErrorCallback(err); }) {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
thread_checker_audio_.DetachFromThread(); thread_checker_audio_.DetachFromThread();
@ -55,16 +60,7 @@ int CoreAudioInput::NumDevices() const {
int CoreAudioInput::SetDevice(int index) { int CoreAudioInput::SetDevice(int index) {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << index; RTC_DLOG(INFO) << __FUNCTION__ << ": " << index;
RTC_DCHECK_RUN_ON(&thread_checker_); return CoreAudioBase::SetDevice(index);
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) { int CoreAudioInput::SetDevice(AudioDeviceModule::WindowsDeviceType device) {
@ -96,21 +92,20 @@ bool CoreAudioInput::RecordingIsInitialized() const {
int CoreAudioInput::InitRecording() { int CoreAudioInput::InitRecording() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(!initialized_); RTC_DCHECK(!initialized_);
RTC_DCHECK(!Recording()); RTC_DCHECK(!Recording());
RTC_DCHECK(!audio_client_.Get()); RTC_DCHECK(!audio_capture_client_);
RTC_DCHECK(!audio_capture_client_.Get());
// Create an IAudioClient and store the valid interface pointer in // Creates an IAudioClient instance and stores the valid interface pointer in
// |audio_client_|. The base class will use optimal input parameters and do // |audio_client3_|, |audio_client2_|, or |audio_client_| depending on
// platform support. The base class will use optimal input parameters and do
// an event driven shared mode initialization. The utilized format will be // an event driven shared mode initialization. The utilized format will be
// stored in |format_| and can be used for configuration and allocation of // stored in |format_| and can be used for configuration and allocation of
// audio buffers. // audio buffers.
if (!CoreAudioBase::Init()) { if (!CoreAudioBase::Init()) {
return -1; return -1;
} }
RTC_DCHECK(audio_client_.Get()); RTC_DCHECK(audio_client_);
// Configure the recording side of the audio device buffer using |format_| // Configure the recording side of the audio device buffer using |format_|
// after a trivial sanity check of the format structure. // after a trivial sanity check of the format structure.
@ -123,7 +118,7 @@ int CoreAudioInput::InitRecording() {
// Create a modified audio buffer class which allows us to supply any number // 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 // of samples (and not only multiple of 10ms) to match the optimal buffer
// size per callback used by Core Audio. // size per callback used by Core Audio.
// TODO(henrika): can we share one FineAudioBuffer? // TODO(henrika): can we share one FineAudioBuffer with the output side?
fine_audio_buffer_ = absl::make_unique<FineAudioBuffer>(audio_device_buffer_); fine_audio_buffer_ = absl::make_unique<FineAudioBuffer>(audio_device_buffer_);
// Create an IAudioCaptureClient for an initialized IAudioClient. // Create an IAudioCaptureClient for an initialized IAudioClient.
@ -131,7 +126,7 @@ int CoreAudioInput::InitRecording() {
// a capture endpoint buffer. // a capture endpoint buffer.
ComPtr<IAudioCaptureClient> audio_capture_client = ComPtr<IAudioCaptureClient> audio_capture_client =
core_audio_utility::CreateCaptureClient(audio_client_.Get()); core_audio_utility::CreateCaptureClient(audio_client_.Get());
if (!audio_capture_client.Get()) { if (!audio_capture_client) {
return -1; return -1;
} }
@ -144,8 +139,7 @@ int CoreAudioInput::InitRecording() {
qpc_to_100ns_ = 10000000.0 / qpc_ticks_per_second; qpc_to_100ns_ = 10000000.0 / qpc_ticks_per_second;
} }
// Store valid COM interfaces. Note that, |audio_client_| has already been // Store valid COM interfaces.
// set in CoreAudioBase::Init().
audio_capture_client_ = audio_capture_client; audio_capture_client_ = audio_capture_client;
initialized_ = true; initialized_ = true;
@ -154,7 +148,6 @@ int CoreAudioInput::InitRecording() {
int CoreAudioInput::StartRecording() { int CoreAudioInput::StartRecording() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(!Recording()); RTC_DCHECK(!Recording());
if (!initialized_) { if (!initialized_) {
RTC_DLOG(LS_WARNING) RTC_DLOG(LS_WARNING)
@ -169,12 +162,12 @@ int CoreAudioInput::StartRecording() {
return -1; return -1;
} }
is_active_ = true;
return 0; return 0;
} }
int CoreAudioInput::StopRecording() { int CoreAudioInput::StopRecording() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
if (!initialized_) { if (!initialized_) {
return 0; return 0;
} }
@ -183,8 +176,7 @@ int CoreAudioInput::StopRecording() {
// method is called without any active input audio. // method is called without any active input audio.
if (!Recording()) { if (!Recording()) {
RTC_DLOG(WARNING) << "No input stream is active"; RTC_DLOG(WARNING) << "No input stream is active";
audio_client_.Reset(); ReleaseCOMObjects();
audio_capture_client_.Reset();
initialized_ = false; initialized_ = false;
return 0; return 0;
} }
@ -194,22 +186,19 @@ int CoreAudioInput::StopRecording() {
return -1; return -1;
} }
// TODO(henrika): if we want to support Init(), Start(), Stop(), Init(), // Release all allocated resources to allow for a restart without
// Start(), Stop() without close in between, these lines are needed. // intermediate destruction.
// Not supported on mobile ADMs, hence we can probably live without it. ReleaseCOMObjects();
// audio_client_.Reset();
// audio_capture_client_.Reset();
// audio_device_buffer_->NativeAudioRecordingInterrupted();
thread_checker_audio_.DetachFromThread();
qpc_to_100ns_.reset(); qpc_to_100ns_.reset();
initialized_ = false; initialized_ = false;
is_active_ = false;
return 0; return 0;
} }
bool CoreAudioInput::Recording() { bool CoreAudioInput::Recording() {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << (audio_thread_ != nullptr); RTC_DLOG(INFO) << __FUNCTION__ << ": " << is_active_;
RTC_DCHECK_RUN_ON(&thread_checker_); return is_active_;
return audio_thread_ != nullptr;
} }
// TODO(henrika): finalize support of audio session volume control. As is, we // TODO(henrika): finalize support of audio session volume control. As is, we
@ -221,12 +210,60 @@ int CoreAudioInput::VolumeIsAvailable(bool* available) {
return IsVolumeControlAvailable(available) ? 0 : -1; return IsVolumeControlAvailable(available) ? 0 : -1;
} }
// Triggers the restart sequence. Only used for testing purposes to emulate
// a real event where e.g. an active input device is removed.
int CoreAudioInput::RestartRecording() {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
if (!Recording()) {
return 0;
}
if (!Restart()) {
RTC_LOG(LS_ERROR) << "RestartRecording failed";
return -1;
}
return 0;
}
bool CoreAudioInput::Restarting() const {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
return IsRestarting();
}
int CoreAudioInput::SetSampleRate(uint32_t sample_rate) {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
sample_rate_ = sample_rate;
return 0;
}
void CoreAudioInput::ReleaseCOMObjects() {
RTC_DLOG(INFO) << __FUNCTION__;
CoreAudioBase::ReleaseCOMObjects();
if (audio_capture_client_.Get()) {
audio_capture_client_.Reset();
}
}
bool CoreAudioInput::OnDataCallback(uint64_t device_frequency) { bool CoreAudioInput::OnDataCallback(uint64_t device_frequency) {
RTC_DCHECK_RUN_ON(&thread_checker_audio_); RTC_DCHECK_RUN_ON(&thread_checker_audio_);
if (num_data_callbacks_ == 0) {
RTC_LOG(INFO) << "--- Input audio stream is alive ---";
}
UINT32 num_frames_in_next_packet = 0; UINT32 num_frames_in_next_packet = 0;
_com_error error = _com_error error =
audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet); audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet);
if (error.Error() != S_OK) { if (error.Error() == AUDCLNT_E_DEVICE_INVALIDATED) {
// Avoid breaking the thread loop implicitly by returning false and return
// true instead for AUDCLNT_E_DEVICE_INVALIDATED even it is a valid error
// message. We will use notifications about device changes instead to stop
// data callbacks and attempt to restart streaming .
RTC_DLOG(LS_ERROR) << "AUDCLNT_E_DEVICE_INVALIDATED";
return true;
}
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetNextPacketSize failed: " RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetNextPacketSize failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
@ -248,16 +285,28 @@ bool CoreAudioInput::OnDataCallback(uint64_t device_frequency) {
RTC_DCHECK_EQ(num_frames_to_read, 0u); RTC_DCHECK_EQ(num_frames_to_read, 0u);
return true; return true;
} }
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetBuffer failed: " RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetBuffer failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
} }
// TODO(henrika): only update the latency estimate N times per second to // Update input delay estimate but only about once per second to save
// save resources. // resources. The estimate is usually stable.
// TODO(henrika): note that FineAudioBuffer adds latency as well. if (num_data_callbacks_ % 100 == 0) {
auto opt_record_delay_ms = EstimateLatencyMillis(capture_time_100ns); absl::optional<int> opt_record_delay_ms;
// TODO(henrika): note that FineAudioBuffer adds latency as well.
opt_record_delay_ms = EstimateLatencyMillis(capture_time_100ns);
if (opt_record_delay_ms) {
latency_ms_ = *opt_record_delay_ms;
} else {
RTC_DLOG(LS_WARNING) << "Input latency is set to fixed value";
latency_ms_ = 20;
}
}
if (num_data_callbacks_ % 500 == 0) {
RTC_DLOG(INFO) << "latency: " << latency_ms_;
}
// The data in the packet is not correlated with the previous packet's // The data in the packet is not correlated with the previous packet's
// device position; possibly due to a stream state transition or timing // device position; possibly due to a stream state transition or timing
@ -283,21 +332,15 @@ bool CoreAudioInput::OnDataCallback(uint64_t device_frequency) {
} else { } else {
// Copy recorded audio in |audio_data| to the WebRTC sink using the // Copy recorded audio in |audio_data| to the WebRTC sink using the
// FineAudioBuffer object. // 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( fine_audio_buffer_->DeliverRecordedData(
rtc::MakeArrayView(reinterpret_cast<const int16_t*>(audio_data), rtc::MakeArrayView(reinterpret_cast<const int16_t*>(audio_data),
format_.Format.nChannels * num_frames_to_read), format_.Format.nChannels * num_frames_to_read),
record_delay_ms); latency_ms_);
} }
error = audio_capture_client_->ReleaseBuffer(num_frames_to_read); error = audio_capture_client_->ReleaseBuffer(num_frames_to_read);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioCaptureClient::ReleaseBuffer failed: " RTC_LOG(LS_ERROR) << "IAudioCaptureClient::ReleaseBuffer failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
@ -305,13 +348,24 @@ bool CoreAudioInput::OnDataCallback(uint64_t device_frequency) {
error = error =
audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet); audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetNextPacketSize failed: " RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetNextPacketSize failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
} }
} }
++num_data_callbacks_;
return true;
}
bool CoreAudioInput::OnErrorCallback(ErrorType error) {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << as_integer(error);
RTC_DCHECK_RUN_ON(&thread_checker_audio_);
if (error == CoreAudioBase::ErrorType::kStreamDisconnected) {
HandleStreamDisconnected();
} else {
RTC_DLOG(WARNING) << "Unsupported error type";
}
return true; return true;
} }
@ -338,5 +392,38 @@ absl::optional<int> CoreAudioInput::EstimateLatencyMillis(
return delay_us.ms(); return delay_us.ms();
} }
// Called from OnErrorCallback() when error type is kStreamDisconnected.
// Note that this method is called on the audio thread and the internal restart
// sequence is also executed on that same thread. The audio thread is therefore
// not stopped during restart. Such a scheme also makes the restart process less
// complex.
// Note that, none of the called methods are thread checked since they can also
// be called on the main thread. Thread checkers are instead added on one layer
// above (in audio_device_module.cc) which ensures that the public API is thread
// safe.
// TODO(henrika): add more details.
bool CoreAudioInput::HandleStreamDisconnected() {
RTC_DLOG(INFO) << "<<<--- " << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_audio_);
if (StopRecording() != 0) {
return false;
}
if (!SwitchDeviceIfNeeded()) {
return false;
}
if (InitRecording() != 0) {
return false;
}
if (StartRecording() != 0) {
return false;
}
RTC_DLOG(INFO) << __FUNCTION__ << " --->>>";
return true;
}
} // namespace webrtc_win } // namespace webrtc_win
} // namespace webrtc } // namespace webrtc

View File

@ -47,13 +47,19 @@ class CoreAudioInput final : public CoreAudioBase, public AudioInput {
int StopRecording() override; int StopRecording() override;
bool Recording() override; bool Recording() override;
int VolumeIsAvailable(bool* available) override; int VolumeIsAvailable(bool* available) override;
int RestartRecording() override;
bool Restarting() const override;
int SetSampleRate(uint32_t sample_rate) override;
CoreAudioInput(const CoreAudioInput&) = delete; CoreAudioInput(const CoreAudioInput&) = delete;
CoreAudioInput& operator=(const CoreAudioInput&) = delete; CoreAudioInput& operator=(const CoreAudioInput&) = delete;
private: private:
void ReleaseCOMObjects();
bool OnDataCallback(uint64_t device_frequency); bool OnDataCallback(uint64_t device_frequency);
bool OnErrorCallback(ErrorType error);
absl::optional<int> EstimateLatencyMillis(uint64_t capture_time_100ns); absl::optional<int> EstimateLatencyMillis(uint64_t capture_time_100ns);
bool HandleStreamDisconnected();
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_; std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
Microsoft::WRL::ComPtr<IAudioCaptureClient> audio_capture_client_; Microsoft::WRL::ComPtr<IAudioCaptureClient> audio_capture_client_;

View File

@ -24,7 +24,8 @@ namespace webrtc_win {
CoreAudioOutput::CoreAudioOutput() CoreAudioOutput::CoreAudioOutput()
: CoreAudioBase(CoreAudioBase::Direction::kOutput, : CoreAudioBase(CoreAudioBase::Direction::kOutput,
[this](uint64_t freq) { return OnDataCallback(freq); }) { [this](uint64_t freq) { return OnDataCallback(freq); },
[this](ErrorType err) { return OnErrorCallback(err); }) {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
thread_checker_audio_.DetachFromThread(); thread_checker_audio_.DetachFromThread();
@ -57,15 +58,7 @@ int CoreAudioOutput::NumDevices() const {
int CoreAudioOutput::SetDevice(int index) { int CoreAudioOutput::SetDevice(int index) {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << index; RTC_DLOG(INFO) << __FUNCTION__ << ": " << index;
RTC_DCHECK_RUN_ON(&thread_checker_); RTC_DCHECK_RUN_ON(&thread_checker_);
if (initialized_) { return CoreAudioBase::SetDevice(index);
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) { int CoreAudioOutput::SetDevice(AudioDeviceModule::WindowsDeviceType device) {
@ -96,22 +89,21 @@ bool CoreAudioOutput::PlayoutIsInitialized() const {
} }
int CoreAudioOutput::InitPlayout() { int CoreAudioOutput::InitPlayout() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__ << ": " << IsRestarting();
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(!initialized_); RTC_DCHECK(!initialized_);
RTC_DCHECK(!Playing()); RTC_DCHECK(!Playing());
RTC_DCHECK(!audio_client_.Get()); RTC_DCHECK(!audio_render_client_);
RTC_DCHECK(!audio_render_client_.Get());
// Create an IAudioClient client and store the valid interface pointer in // Creates an IAudioClient instance and stores the valid interface pointer in
// |audio_client_|. The base class will use optimal output parameters and do // |audio_client3_|, |audio_client2_|, or |audio_client_| depending on
// platform support. The base class will use optimal output parameters and do
// an event driven shared mode initialization. The utilized format will be // an event driven shared mode initialization. The utilized format will be
// stored in |format_| and can be used for configuration and allocation of // stored in |format_| and can be used for configuration and allocation of
// audio buffers. // audio buffers.
if (!CoreAudioBase::Init()) { if (!CoreAudioBase::Init()) {
return -1; return -1;
} }
RTC_DCHECK(audio_client_.Get()); RTC_DCHECK(audio_client_);
// Configure the playout side of the audio device buffer using |format_| // Configure the playout side of the audio device buffer using |format_|
// after a trivial sanity check of the format structure. // after a trivial sanity check of the format structure.
@ -124,7 +116,7 @@ int CoreAudioOutput::InitPlayout() {
// Create a modified audio buffer class which allows us to ask for any number // 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 // of samples (and not only multiple of 10ms) to match the optimal
// buffer size per callback used by Core Audio. // buffer size per callback used by Core Audio.
// TODO(henrika): can we use a shared buffer instead? // TODO(henrika): can we share one FineAudioBuffer with the input side?
fine_audio_buffer_ = absl::make_unique<FineAudioBuffer>(audio_device_buffer_); fine_audio_buffer_ = absl::make_unique<FineAudioBuffer>(audio_device_buffer_);
// Create an IAudioRenderClient for an initialized IAudioClient. // Create an IAudioRenderClient for an initialized IAudioClient.
@ -132,16 +124,17 @@ int CoreAudioOutput::InitPlayout() {
// a rendering endpoint buffer. // a rendering endpoint buffer.
ComPtr<IAudioRenderClient> audio_render_client = ComPtr<IAudioRenderClient> audio_render_client =
core_audio_utility::CreateRenderClient(audio_client_.Get()); core_audio_utility::CreateRenderClient(audio_client_.Get());
if (!audio_render_client.Get()) if (!audio_render_client.Get()) {
return -1; return -1;
}
ComPtr<IAudioClock> audio_clock = ComPtr<IAudioClock> audio_clock =
core_audio_utility::CreateAudioClock(audio_client_.Get()); core_audio_utility::CreateAudioClock(audio_client_.Get());
if (!audio_clock.Get()) if (!audio_clock.Get()) {
return -1; return -1;
}
// Store valid COM interfaces. Note that, |audio_client_| has already been // Store valid COM interfaces.
// set in CoreAudioBase::Init().
audio_render_client_ = audio_render_client; audio_render_client_ = audio_render_client;
audio_clock_ = audio_clock; audio_clock_ = audio_clock;
@ -150,13 +143,11 @@ int CoreAudioOutput::InitPlayout() {
} }
int CoreAudioOutput::StartPlayout() { int CoreAudioOutput::StartPlayout() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__ << ": " << IsRestarting();
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(!Playing()); RTC_DCHECK(!Playing());
if (!initialized_) { if (!initialized_) {
RTC_DLOG(LS_WARNING) RTC_DLOG(LS_WARNING)
<< "Playout can not start since InitPlayout must succeed first"; << "Playout can not start since InitPlayout must succeed first";
return 0;
} }
if (fine_audio_buffer_) { if (fine_audio_buffer_) {
fine_audio_buffer_->ResetPlayout(); fine_audio_buffer_->ResetPlayout();
@ -173,12 +164,12 @@ int CoreAudioOutput::StartPlayout() {
return -1; return -1;
} }
is_active_ = true;
return 0; return 0;
} }
int CoreAudioOutput::StopPlayout() { int CoreAudioOutput::StopPlayout() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__ << ": " << IsRestarting();
RTC_DCHECK_RUN_ON(&thread_checker_);
if (!initialized_) { if (!initialized_) {
return 0; return 0;
} }
@ -187,8 +178,7 @@ int CoreAudioOutput::StopPlayout() {
// method is called without any active output audio. // method is called without any active output audio.
if (!Playing()) { if (!Playing()) {
RTC_DLOG(WARNING) << "No output stream is active"; RTC_DLOG(WARNING) << "No output stream is active";
audio_client_.Reset(); ReleaseCOMObjects();
audio_render_client_.Reset();
initialized_ = false; initialized_ = false;
return 0; return 0;
} }
@ -198,15 +188,18 @@ int CoreAudioOutput::StopPlayout() {
return -1; return -1;
} }
thread_checker_audio_.DetachFromThread(); // Release all allocated resources to allow for a restart without
// intermediate destruction.
ReleaseCOMObjects();
initialized_ = false; initialized_ = false;
is_active_ = false;
return 0; return 0;
} }
bool CoreAudioOutput::Playing() { bool CoreAudioOutput::Playing() {
RTC_DLOG(INFO) << __FUNCTION__; RTC_DLOG(INFO) << __FUNCTION__ << ": " << is_active_;
RTC_DCHECK_RUN_ON(&thread_checker_); return is_active_;
return audio_thread_ != nullptr;
} }
// TODO(henrika): finalize support of audio session volume control. As is, we // TODO(henrika): finalize support of audio session volume control. As is, we
@ -218,13 +211,75 @@ int CoreAudioOutput::VolumeIsAvailable(bool* available) {
return IsVolumeControlAvailable(available) ? 0 : -1; return IsVolumeControlAvailable(available) ? 0 : -1;
} }
// Triggers the restart sequence. Only used for testing purposes to emulate
// a real event where e.g. an active output device is removed.
int CoreAudioOutput::RestartPlayout() {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
if (!Playing()) {
return 0;
}
if (!Restart()) {
RTC_LOG(LS_ERROR) << "RestartPlayout failed";
return -1;
}
return 0;
}
bool CoreAudioOutput::Restarting() const {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
return IsRestarting();
}
int CoreAudioOutput::SetSampleRate(uint32_t sample_rate) {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
sample_rate_ = sample_rate;
return 0;
}
void CoreAudioOutput::ReleaseCOMObjects() {
RTC_DLOG(INFO) << __FUNCTION__;
CoreAudioBase::ReleaseCOMObjects();
if (audio_render_client_.Get()) {
audio_render_client_.Reset();
}
}
bool CoreAudioOutput::OnErrorCallback(ErrorType error) {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << as_integer(error);
RTC_DCHECK_RUN_ON(&thread_checker_audio_);
if (!initialized_ || !Playing()) {
return true;
}
if (error == CoreAudioBase::ErrorType::kStreamDisconnected) {
HandleStreamDisconnected();
} else {
RTC_DLOG(WARNING) << "Unsupported error type";
}
return true;
}
bool CoreAudioOutput::OnDataCallback(uint64_t device_frequency) { bool CoreAudioOutput::OnDataCallback(uint64_t device_frequency) {
RTC_DCHECK_RUN_ON(&thread_checker_audio_); RTC_DCHECK_RUN_ON(&thread_checker_audio_);
if (num_data_callbacks_ == 0) {
RTC_LOG(INFO) << "--- Output audio stream is alive ---";
}
// Get the padding value which indicates the amount of valid unread data that // Get the padding value which indicates the amount of valid unread data that
// the endpoint buffer currently contains. // the endpoint buffer currently contains.
UINT32 num_unread_frames = 0; UINT32 num_unread_frames = 0;
_com_error error = audio_client_->GetCurrentPadding(&num_unread_frames); _com_error error = audio_client_->GetCurrentPadding(&num_unread_frames);
if (error.Error() != S_OK) { if (error.Error() == AUDCLNT_E_DEVICE_INVALIDATED) {
// Avoid breaking the thread loop implicitly by returning false and return
// true instead for AUDCLNT_E_DEVICE_INVALIDATED even it is a valid error
// message. We will use notifications about device changes instead to stop
// data callbacks and attempt to restart streaming .
RTC_DLOG(LS_ERROR) << "AUDCLNT_E_DEVICE_INVALIDATED";
return true;
}
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
@ -236,39 +291,49 @@ bool CoreAudioOutput::OnDataCallback(uint64_t device_frequency) {
// calling IAudioRenderClient::GetBuffer(). // calling IAudioRenderClient::GetBuffer().
UINT32 num_requested_frames = UINT32 num_requested_frames =
endpoint_buffer_size_frames_ - num_unread_frames; endpoint_buffer_size_frames_ - num_unread_frames;
if (num_requested_frames == 0) {
RTC_DLOG(LS_WARNING)
<< "Audio thread is signaled but no new audio samples are needed";
return true;
}
// Request all available space in the rendering endpoint buffer into which the // Request all available space in the rendering endpoint buffer into which the
// client can later write an audio packet. // client can later write an audio packet.
uint8_t* audio_data; uint8_t* audio_data;
error = audio_render_client_->GetBuffer(num_requested_frames, &audio_data); error = audio_render_client_->GetBuffer(num_requested_frames, &audio_data);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: " RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
} }
// TODO(henrika): only update the latency estimate N times per second to // Update output delay estimate but only about once per second to save
// save resources. // resources. The estimate is usually stable.
// TODO(henrika): note that FineAudioBuffer adds latency as well. if (num_data_callbacks_ % 100 == 0) {
int playout_delay_ms = EstimateOutputLatencyMillis(device_frequency); // TODO(henrika): note that FineAudioBuffer adds latency as well.
// RTC_DLOG(INFO) << "playout_delay_ms: " << playout_delay_ms; latency_ms_ = EstimateOutputLatencyMillis(device_frequency);
if (num_data_callbacks_ % 500 == 0) {
RTC_DLOG(INFO) << "latency: " << latency_ms_;
}
}
// Get audio data from WebRTC and write it to the allocated buffer in // Get audio data from WebRTC and write it to the allocated buffer in
// |audio_data|. // |audio_data|. The playout latency is not updated for each callback.
fine_audio_buffer_->GetPlayoutData( fine_audio_buffer_->GetPlayoutData(
rtc::MakeArrayView(reinterpret_cast<int16_t*>(audio_data), rtc::MakeArrayView(reinterpret_cast<int16_t*>(audio_data),
num_requested_frames * format_.Format.nChannels), num_requested_frames * format_.Format.nChannels),
playout_delay_ms); latency_ms_);
// Release the buffer space acquired in IAudioRenderClient::GetBuffer. // Release the buffer space acquired in IAudioRenderClient::GetBuffer.
error = audio_render_client_->ReleaseBuffer(num_requested_frames, 0); error = audio_render_client_->ReleaseBuffer(num_requested_frames, 0);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: " RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: "
<< core_audio_utility::ErrorToString(error); << core_audio_utility::ErrorToString(error);
return false; return false;
} }
num_frames_written_ += num_requested_frames; num_frames_written_ += num_requested_frames;
++num_data_callbacks_;
return true; return true;
} }
@ -301,6 +366,39 @@ int CoreAudioOutput::EstimateOutputLatencyMillis(uint64_t device_frequency) {
return delay_ms; return delay_ms;
} }
// Called from OnErrorCallback() when error type is kStreamDisconnected.
// Note that this method is called on the audio thread and the internal restart
// sequence is also executed on that same thread. The audio thread is therefore
// not stopped during restart. Such a scheme also makes the restart process less
// complex.
// Note that, none of the called methods are thread checked since they can also
// be called on the main thread. Thread checkers are instead added on one layer
// above (in audio_device_module.cc) which ensures that the public API is thread
// safe.
// TODO(henrika): add more details.
bool CoreAudioOutput::HandleStreamDisconnected() {
RTC_DLOG(INFO) << "<<<--- " << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_audio_);
if (StopPlayout() != 0) {
return false;
}
if (!SwitchDeviceIfNeeded()) {
return false;
}
if (InitPlayout() != 0) {
return false;
}
if (StartPlayout() != 0) {
return false;
}
RTC_DLOG(INFO) << __FUNCTION__ << " --->>>";
return true;
}
} // namespace webrtc_win } // namespace webrtc_win
} // namespace webrtc } // namespace webrtc

View File

@ -47,13 +47,19 @@ class CoreAudioOutput final : public CoreAudioBase, public AudioOutput {
int StopPlayout() override; int StopPlayout() override;
bool Playing() override; bool Playing() override;
int VolumeIsAvailable(bool* available) override; int VolumeIsAvailable(bool* available) override;
int RestartPlayout() override;
bool Restarting() const override;
int SetSampleRate(uint32_t sample_rate) override;
CoreAudioOutput(const CoreAudioOutput&) = delete; CoreAudioOutput(const CoreAudioOutput&) = delete;
CoreAudioOutput& operator=(const CoreAudioOutput&) = delete; CoreAudioOutput& operator=(const CoreAudioOutput&) = delete;
private: private:
void ReleaseCOMObjects();
bool OnDataCallback(uint64_t device_frequency); bool OnDataCallback(uint64_t device_frequency);
bool OnErrorCallback(ErrorType error);
int EstimateOutputLatencyMillis(uint64_t device_frequency); int EstimateOutputLatencyMillis(uint64_t device_frequency);
bool HandleStreamDisconnected();
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_; std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
Microsoft::WRL::ComPtr<IAudioRenderClient> audio_render_client_; Microsoft::WRL::ComPtr<IAudioRenderClient> audio_render_client_;

View File

@ -24,6 +24,7 @@
#include "rtc_base/platform_thread_types.h" #include "rtc_base/platform_thread_types.h"
#include "rtc_base/strings/string_builder.h" #include "rtc_base/strings/string_builder.h"
#include "rtc_base/stringutils.h" #include "rtc_base/stringutils.h"
#include "rtc_base/win/windows_version.h"
using ATL::CComHeapPtr; using ATL::CComHeapPtr;
using Microsoft::WRL::ComPtr; using Microsoft::WRL::ComPtr;
@ -61,7 +62,7 @@ ComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
_com_error error = _com_error error =
::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&device_enumerator)); IID_PPV_ARGS(&device_enumerator));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error); RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error);
} }
@ -72,10 +73,10 @@ ComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
// modules. Calling CoInitializeEx() is an attempt to resolve the reported // modules. Calling CoInitializeEx() is an attempt to resolve the reported
// issues. See http://crbug.com/378465 for details. // issues. See http://crbug.com/378465 for details.
error = CoInitializeEx(nullptr, COINIT_MULTITHREADED); error = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_ALL, IID_PPV_ARGS(&device_enumerator)); CLSCTX_ALL, IID_PPV_ARGS(&device_enumerator));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " RTC_LOG(LS_ERROR) << "CoCreateInstance failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
@ -131,7 +132,7 @@ ComPtr<IMMDevice> CreateDeviceInternal(const std::string& device_id,
if (device_id == AudioDeviceName::kDefaultDeviceId) { if (device_id == AudioDeviceName::kDefaultDeviceId) {
error = device_enum->GetDefaultAudioEndpoint( error = device_enum->GetDefaultAudioEndpoint(
data_flow, role, audio_endpoint_device.GetAddressOf()); data_flow, role, audio_endpoint_device.GetAddressOf());
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) RTC_LOG(LS_ERROR)
<< "IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: " << "IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: "
<< ErrorToString(error); << ErrorToString(error);
@ -139,7 +140,7 @@ ComPtr<IMMDevice> CreateDeviceInternal(const std::string& device_id,
} else { } else {
error = device_enum->GetDevice(rtc::ToUtf16(device_id).c_str(), error = device_enum->GetDevice(rtc::ToUtf16(device_id).c_str(),
audio_endpoint_device.GetAddressOf()); audio_endpoint_device.GetAddressOf());
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDeviceEnumerator::GetDevice failed: " RTC_LOG(LS_ERROR) << "IMMDeviceEnumerator::GetDevice failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
@ -199,7 +200,7 @@ ComPtr<IAudioSessionManager2> CreateSessionManager2Internal(
_com_error error = _com_error error =
audio_device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, audio_device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
nullptr, &audio_session_manager); nullptr, &audio_session_manager);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioSessionManager2) failed: " RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioSessionManager2) failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
@ -220,7 +221,7 @@ ComPtr<IAudioSessionEnumerator> CreateSessionEnumeratorInternal(
} }
_com_error error = _com_error error =
audio_session_manager->GetSessionEnumerator(&audio_session_enumerator); audio_session_manager->GetSessionEnumerator(&audio_session_enumerator);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) RTC_LOG(LS_ERROR)
<< "IAudioSessionEnumerator::IAudioSessionEnumerator failed: " << "IAudioSessionEnumerator::IAudioSessionEnumerator failed: "
<< ErrorToString(error); << ErrorToString(error);
@ -238,7 +239,7 @@ ComPtr<IAudioClient> CreateClientInternal(IMMDevice* audio_device) {
ComPtr<IAudioClient> audio_client; ComPtr<IAudioClient> audio_client;
_com_error error = audio_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, _com_error error = audio_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
nullptr, &audio_client); nullptr, &audio_client);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient) failed: " RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient) failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
@ -252,13 +253,27 @@ ComPtr<IAudioClient2> CreateClient2Internal(IMMDevice* audio_device) {
ComPtr<IAudioClient2> audio_client; ComPtr<IAudioClient2> audio_client;
_com_error error = audio_device->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, _com_error error = audio_device->Activate(__uuidof(IAudioClient2), CLSCTX_ALL,
nullptr, &audio_client); nullptr, &audio_client);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient2) failed: " RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient2) failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
return audio_client; return audio_client;
} }
ComPtr<IAudioClient3> CreateClient3Internal(IMMDevice* audio_device) {
if (!audio_device)
return ComPtr<IAudioClient3>();
ComPtr<IAudioClient3> audio_client;
_com_error error = audio_device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL,
nullptr, &audio_client);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient3) failed: "
<< ErrorToString(error);
}
return audio_client;
}
ComPtr<IMMDeviceCollection> CreateCollectionInternal(EDataFlow data_flow) { ComPtr<IMMDeviceCollection> CreateCollectionInternal(EDataFlow data_flow) {
ComPtr<IMMDeviceEnumerator> device_enumerator( ComPtr<IMMDeviceEnumerator> device_enumerator(
CreateDeviceEnumeratorInternal(true)); CreateDeviceEnumeratorInternal(true));
@ -272,7 +287,7 @@ ComPtr<IMMDeviceCollection> CreateCollectionInternal(EDataFlow data_flow) {
ComPtr<IMMDeviceCollection> collection; ComPtr<IMMDeviceCollection> collection;
_com_error error = device_enumerator->EnumAudioEndpoints( _com_error error = device_enumerator->EnumAudioEndpoints(
data_flow, DEVICE_STATE_ACTIVE, collection.GetAddressOf()); data_flow, DEVICE_STATE_ACTIVE, collection.GetAddressOf());
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDeviceCollection::EnumAudioEndpoints failed: " RTC_LOG(LS_ERROR) << "IMMDeviceCollection::EnumAudioEndpoints failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
@ -338,7 +353,7 @@ bool GetDeviceNamesInternal(EDataFlow data_flow,
// Retrieve a pointer to the specified item in the device collection. // Retrieve a pointer to the specified item in the device collection.
ComPtr<IMMDevice> audio_device; ComPtr<IMMDevice> audio_device;
_com_error error = collection->Item(i, audio_device.GetAddressOf()); _com_error error = collection->Item(i, audio_device.GetAddressOf());
if (error.Error() != S_OK) if (FAILED(error.Error()))
continue; continue;
// Retrieve the complete device name for the given audio device endpoint. // Retrieve the complete device name for the given audio device endpoint.
AudioDeviceName device_name( AudioDeviceName device_name(
@ -354,7 +369,8 @@ bool GetDeviceNamesInternal(EDataFlow data_flow,
} }
HRESULT GetPreferredAudioParametersInternal(IAudioClient* client, HRESULT GetPreferredAudioParametersInternal(IAudioClient* client,
AudioParameters* params) { AudioParameters* params,
int fixed_sample_rate) {
WAVEFORMATPCMEX mix_format; WAVEFORMATPCMEX mix_format;
HRESULT hr = core_audio_utility::GetSharedModeMixFormat(client, &mix_format); HRESULT hr = core_audio_utility::GetSharedModeMixFormat(client, &mix_format);
if (FAILED(hr)) if (FAILED(hr))
@ -366,7 +382,14 @@ HRESULT GetPreferredAudioParametersInternal(IAudioClient* client,
if (FAILED(hr)) if (FAILED(hr))
return hr; return hr;
const int sample_rate = mix_format.Format.nSamplesPerSec; int sample_rate = mix_format.Format.nSamplesPerSec;
// Override default sample rate if |fixed_sample_rate| is set and different
// from the default rate.
if (fixed_sample_rate > 0 && fixed_sample_rate != sample_rate) {
RTC_DLOG(INFO) << "Using fixed sample rate instead of the preferred: "
<< sample_rate << " is replaced by " << fixed_sample_rate;
sample_rate = fixed_sample_rate;
}
// TODO(henrika): utilize full mix_format.Format.wBitsPerSample. // TODO(henrika): utilize full mix_format.Format.wBitsPerSample.
// const size_t bits_per_sample = AudioParameters::kBitsPerSample; // const size_t bits_per_sample = AudioParameters::kBitsPerSample;
// TODO(henrika): improve channel layout support. // TODO(henrika): improve channel layout support.
@ -427,6 +450,16 @@ int NumberOfActiveDevices(EDataFlow data_flow) {
return static_cast<int>(number_of_active_devices); return static_cast<int>(number_of_active_devices);
} }
uint32_t GetAudioClientVersion() {
uint32_t version = 1;
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN10) {
version = 3;
} else if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN8) {
version = 2;
}
return version;
}
ComPtr<IMMDeviceEnumerator> CreateDeviceEnumerator() { ComPtr<IMMDeviceEnumerator> CreateDeviceEnumerator() {
RTC_DLOG(INFO) << "CreateDeviceEnumerator"; RTC_DLOG(INFO) << "CreateDeviceEnumerator";
return CreateDeviceEnumeratorInternal(true); return CreateDeviceEnumeratorInternal(true);
@ -494,7 +527,7 @@ EDataFlow GetDataFlow(IMMDevice* device) {
RTC_DCHECK(device); RTC_DCHECK(device);
ComPtr<IMMEndpoint> endpoint; ComPtr<IMMEndpoint> endpoint;
_com_error error = device->QueryInterface(endpoint.GetAddressOf()); _com_error error = device->QueryInterface(endpoint.GetAddressOf());
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMDevice::QueryInterface failed: " RTC_LOG(LS_ERROR) << "IMMDevice::QueryInterface failed: "
<< ErrorToString(error); << ErrorToString(error);
return eAll; return eAll;
@ -502,7 +535,7 @@ EDataFlow GetDataFlow(IMMDevice* device) {
EDataFlow data_flow; EDataFlow data_flow;
error = endpoint->GetDataFlow(&data_flow); error = endpoint->GetDataFlow(&data_flow);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IMMEndpoint::GetDataFlow failed: " RTC_LOG(LS_ERROR) << "IMMEndpoint::GetDataFlow failed: "
<< ErrorToString(error); << ErrorToString(error);
return eAll; return eAll;
@ -541,7 +574,7 @@ int NumberOfActiveSessions(IMMDevice* device) {
// Iterate over all audio sessions for the given device. // Iterate over all audio sessions for the given device.
int session_count = 0; int session_count = 0;
_com_error error = session_enumerator->GetCount(&session_count); _com_error error = session_enumerator->GetCount(&session_count);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetCount failed: " RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetCount failed: "
<< ErrorToString(error); << ErrorToString(error);
return 0; return 0;
@ -553,7 +586,7 @@ int NumberOfActiveSessions(IMMDevice* device) {
// Acquire the session control interface. // Acquire the session control interface.
ComPtr<IAudioSessionControl> session_control; ComPtr<IAudioSessionControl> session_control;
error = session_enumerator->GetSession(session, &session_control); error = session_enumerator->GetSession(session, &session_control);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetSession failed: " RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetSession failed: "
<< ErrorToString(error); << ErrorToString(error);
return 0; return 0;
@ -569,7 +602,7 @@ int NumberOfActiveSessions(IMMDevice* device) {
// Get the current state and check if the state is active or not. // Get the current state and check if the state is active or not.
AudioSessionState state; AudioSessionState state;
error = session_control->GetState(&state); error = session_control->GetState(&state);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioSessionControl::GetState failed: " RTC_LOG(LS_ERROR) << "IAudioSessionControl::GetState failed: "
<< ErrorToString(error); << ErrorToString(error);
return 0; return 0;
@ -599,26 +632,87 @@ ComPtr<IAudioClient2> CreateClient2(const std::string& device_id,
return CreateClient2Internal(device.Get()); return CreateClient2Internal(device.Get());
} }
ComPtr<IAudioClient3> CreateClient3(const std::string& device_id,
EDataFlow data_flow,
ERole role) {
RTC_DLOG(INFO) << "CreateClient3";
ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role));
return CreateClient3Internal(device.Get());
}
HRESULT SetClientProperties(IAudioClient2* client) { HRESULT SetClientProperties(IAudioClient2* client) {
RTC_DLOG(INFO) << "SetClientProperties"; RTC_DLOG(INFO) << "SetClientProperties";
RTC_DCHECK(client); RTC_DCHECK(client);
if (GetAudioClientVersion() < 2) {
AudioClientProperties properties = {0}; RTC_LOG(LS_WARNING) << "Requires IAudioClient2 or higher";
properties.cbSize = sizeof(AudioClientProperties); return AUDCLNT_E_UNSUPPORTED_FORMAT;
properties.bIsOffload = false; }
AudioClientProperties props = {0};
props.cbSize = sizeof(AudioClientProperties);
// Real-time VoIP communication. // Real-time VoIP communication.
// TODO(henrika): other categories? // TODO(henrika): other categories?
properties.eCategory = AudioCategory_Communications; props.eCategory = AudioCategory_Communications;
// TODO(henrika): can AUDCLNT_STREAMOPTIONS_RAW be used? // Hardware-offloaded audio processing allows the main audio processing tasks
properties.Options = AUDCLNT_STREAMOPTIONS_NONE; // to be performed outside the computer's main CPU. Check support and log the
_com_error error = client->SetClientProperties(&properties); // result but hard-code |bIsOffload| to FALSE for now.
if (error.Error() != S_OK) { // TODO(henrika): evaluate hardware-offloading. Might complicate usage of
// IAudioClient::GetMixFormat().
BOOL supports_offload = FALSE;
_com_error error =
client->IsOffloadCapable(props.eCategory, &supports_offload);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient2::IsOffloadCapable failed: "
<< ErrorToString(error);
}
RTC_DLOG(INFO) << "supports_offload: " << supports_offload;
props.bIsOffload = false;
// TODO(henrika): pros and cons compared with AUDCLNT_STREAMOPTIONS_NONE?
props.Options |= AUDCLNT_STREAMOPTIONS_NONE;
// Requires System.Devices.AudioDevice.RawProcessingSupported.
// props.Options |= AUDCLNT_STREAMOPTIONS_RAW;
// If it is important to avoid resampling in the audio engine, set this flag.
// props.Options |= AUDCLNT_STREAMOPTIONS_MATCH_FORMAT;
RTC_DLOG(INFO) << "options: 0x" << rtc::ToHex(props.Options);
error = client->SetClientProperties(&props);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient2::SetClientProperties failed: " RTC_LOG(LS_ERROR) << "IAudioClient2::SetClientProperties failed: "
<< ErrorToString(error); << ErrorToString(error);
} }
return error.Error(); return error.Error();
} }
HRESULT GetBufferSizeLimits(IAudioClient2* client,
const WAVEFORMATEXTENSIBLE* format,
REFERENCE_TIME* min_buffer_duration,
REFERENCE_TIME* max_buffer_duration) {
RTC_DLOG(INFO) << "GetBufferSizeLimits";
RTC_DCHECK(client);
if (GetAudioClientVersion() < 2) {
RTC_LOG(LS_WARNING) << "Requires IAudioClient2 or higher";
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}
REFERENCE_TIME min_duration = 0;
REFERENCE_TIME max_duration = 0;
_com_error error =
client->GetBufferSizeLimits(reinterpret_cast<const WAVEFORMATEX*>(format),
TRUE, &min_duration, &max_duration);
if (error.Error() == AUDCLNT_E_OFFLOAD_MODE_ONLY) {
// This API seems to be supported in off-load mode only but it is not
// documented as a valid error code. Making a special note about it here.
RTC_LOG(LS_ERROR) << "IAudioClient2::GetBufferSizeLimits failed: "
<< "AUDCLNT_E_OFFLOAD_MODE_ONLY";
} else if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient2::GetBufferSizeLimits failed: "
<< ErrorToString(error);
} else {
*min_buffer_duration = min_duration;
*max_buffer_duration = max_duration;
RTC_DLOG(INFO) << "min_buffer_duration: " << min_buffer_duration;
RTC_DLOG(INFO) << "max_buffer_duration: " << max_buffer_duration;
}
return error.Error();
}
HRESULT GetSharedModeMixFormat(IAudioClient* client, HRESULT GetSharedModeMixFormat(IAudioClient* client,
WAVEFORMATEXTENSIBLE* format) { WAVEFORMATEXTENSIBLE* format) {
RTC_DLOG(INFO) << "GetSharedModeMixFormat"; RTC_DLOG(INFO) << "GetSharedModeMixFormat";
@ -626,7 +720,7 @@ HRESULT GetSharedModeMixFormat(IAudioClient* client,
ScopedCoMem<WAVEFORMATEXTENSIBLE> format_ex; ScopedCoMem<WAVEFORMATEXTENSIBLE> format_ex;
_com_error error = _com_error error =
client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&format_ex)); client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&format_ex));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetMixFormat failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetMixFormat failed: "
<< ErrorToString(error); << ErrorToString(error);
return error.Error(); return error.Error();
@ -689,7 +783,7 @@ HRESULT GetDevicePeriod(IAudioClient* client,
REFERENCE_TIME default_period = 0; REFERENCE_TIME default_period = 0;
REFERENCE_TIME minimum_period = 0; REFERENCE_TIME minimum_period = 0;
_com_error error = client->GetDevicePeriod(&default_period, &minimum_period); _com_error error = client->GetDevicePeriod(&default_period, &minimum_period);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetDevicePeriod failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetDevicePeriod failed: "
<< ErrorToString(error); << ErrorToString(error);
return error.Error(); return error.Error();
@ -699,13 +793,55 @@ HRESULT GetDevicePeriod(IAudioClient* client,
: minimum_period; : minimum_period;
RTC_LOG(INFO) << "device_period: " RTC_LOG(INFO) << "device_period: "
<< ReferenceTimeToTimeDelta(*device_period).ms() << " [ms]"; << ReferenceTimeToTimeDelta(*device_period).ms() << " [ms]";
RTC_LOG(INFO) << "minimum_period: "
<< ReferenceTimeToTimeDelta(minimum_period).ms() << " [ms]";
return error.Error();
}
HRESULT GetSharedModeEnginePeriod(IAudioClient3* client3,
const WAVEFORMATEXTENSIBLE* format,
uint32_t* default_period_in_frames,
uint32_t* fundamental_period_in_frames,
uint32_t* min_period_in_frames,
uint32_t* max_period_in_frames) {
RTC_DLOG(INFO) << "GetSharedModeEnginePeriod";
RTC_DCHECK(client3);
UINT32 default_period = 0;
UINT32 fundamental_period = 0;
UINT32 min_period = 0;
UINT32 max_period = 0;
_com_error error = client3->GetSharedModeEnginePeriod(
reinterpret_cast<const WAVEFORMATEX*>(format), &default_period,
&fundamental_period, &min_period, &max_period);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient3::GetSharedModeEnginePeriod failed: "
<< ErrorToString(error);
return error.Error();
}
WAVEFORMATEX format_ex = format->Format;
const WORD sample_rate = format_ex.nSamplesPerSec;
RTC_LOG(INFO) << "default_period_in_frames: " << default_period << " ("
<< FramesToMilliseconds(default_period, sample_rate) << " ms)";
RTC_LOG(INFO) << "fundamental_period_in_frames: " << fundamental_period
<< " (" << FramesToMilliseconds(fundamental_period, sample_rate)
<< " ms)";
RTC_LOG(INFO) << "min_period_in_frames: " << min_period << " ("
<< FramesToMilliseconds(min_period, sample_rate) << " ms)";
RTC_LOG(INFO) << "max_period_in_frames: " << max_period << " ("
<< FramesToMilliseconds(max_period, sample_rate) << " ms)";
*default_period_in_frames = default_period;
*fundamental_period_in_frames = fundamental_period;
*min_period_in_frames = min_period;
*max_period_in_frames = max_period;
return error.Error(); return error.Error();
} }
HRESULT GetPreferredAudioParameters(const std::string& device_id, HRESULT GetPreferredAudioParameters(const std::string& device_id,
bool is_output_device, bool is_output_device,
AudioParameters* params) { AudioParameters* params) {
RTC_DLOG(INFO) << "GetPreferredAudioParameters"; RTC_DLOG(INFO) << "GetPreferredAudioParameters: " << is_output_device;
EDataFlow data_flow = is_output_device ? eRender : eCapture; EDataFlow data_flow = is_output_device ? eRender : eCapture;
ComPtr<IMMDevice> device; ComPtr<IMMDevice> device;
if (device_id == AudioDeviceName::kDefaultCommunicationsDeviceId) { if (device_id == AudioDeviceName::kDefaultCommunicationsDeviceId) {
@ -724,21 +860,40 @@ HRESULT GetPreferredAudioParameters(const std::string& device_id,
if (!client.Get()) if (!client.Get())
return E_FAIL; return E_FAIL;
return GetPreferredAudioParametersInternal(client.Get(), params); return GetPreferredAudioParametersInternal(client.Get(), params, -1);
} }
HRESULT GetPreferredAudioParameters(IAudioClient* client, HRESULT GetPreferredAudioParameters(IAudioClient* client,
AudioParameters* params) { AudioParameters* params) {
RTC_DLOG(INFO) << "GetPreferredAudioParameters";
RTC_DCHECK(client); RTC_DCHECK(client);
return GetPreferredAudioParametersInternal(client, params); return GetPreferredAudioParametersInternal(client, params, -1);
}
HRESULT GetPreferredAudioParameters(IAudioClient* client,
webrtc::AudioParameters* params,
uint32_t sample_rate) {
RTC_DLOG(INFO) << "GetPreferredAudioParameters: " << sample_rate;
RTC_DCHECK(client);
return GetPreferredAudioParametersInternal(client, params, sample_rate);
} }
HRESULT SharedModeInitialize(IAudioClient* client, HRESULT SharedModeInitialize(IAudioClient* client,
const WAVEFORMATEXTENSIBLE* format, const WAVEFORMATEXTENSIBLE* format,
HANDLE event_handle, HANDLE event_handle,
REFERENCE_TIME buffer_duration,
bool auto_convert_pcm,
uint32_t* endpoint_buffer_size) { uint32_t* endpoint_buffer_size) {
RTC_DLOG(INFO) << "SharedModeInitialize"; RTC_DLOG(INFO) << "SharedModeInitialize: buffer_duration=" << buffer_duration
<< ", auto_convert_pcm=" << auto_convert_pcm;
RTC_DCHECK(client); RTC_DCHECK(client);
RTC_DCHECK_GE(buffer_duration, 0);
if (buffer_duration != 0) {
RTC_DLOG(LS_WARNING) << "Non-default buffer size is used";
}
if (auto_convert_pcm) {
RTC_DLOG(LS_WARNING) << "Sample rate converter can be utilized";
}
// The AUDCLNT_STREAMFLAGS_NOPERSIST flag disables persistence of the volume // The AUDCLNT_STREAMFLAGS_NOPERSIST flag disables persistence of the volume
// and mute settings for a session that contains rendering streams. // and mute settings for a session that contains rendering streams.
// By default, the volume level and muting state for a rendering session are // By default, the volume level and muting state for a rendering session are
@ -757,13 +912,26 @@ HRESULT SharedModeInitialize(IAudioClient* client,
stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
RTC_DLOG(INFO) << "The stream is initialized to be event driven"; RTC_DLOG(INFO) << "The stream is initialized to be event driven";
} }
// Check if sample-rate conversion is requested.
if (auto_convert_pcm) {
// Add channel matrixer (not utilized here) and rate converter to convert
// from our (the client's) format to the audio engine mix format.
// Currently only supported for testing, i.e., not possible to enable using
// public APIs.
RTC_DLOG(INFO) << "The stream is initialized to support rate conversion";
stream_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
stream_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
}
RTC_DLOG(INFO) << "stream_flags: 0x" << rtc::ToHex(stream_flags); RTC_DLOG(INFO) << "stream_flags: 0x" << rtc::ToHex(stream_flags);
// Initialize the shared mode client for minimal delay. // Initialize the shared mode client for minimal delay if |buffer_duration|
// is 0 or possibly a higher delay (more robust) if |buffer_duration| is
// larger than 0. The actual size is given by IAudioClient::GetBufferSize().
_com_error error = client->Initialize( _com_error error = client->Initialize(
AUDCLNT_SHAREMODE_SHARED, stream_flags, 0, 0, AUDCLNT_SHAREMODE_SHARED, stream_flags, buffer_duration, 0,
reinterpret_cast<const WAVEFORMATEX*>(format), nullptr); reinterpret_cast<const WAVEFORMATEX*>(format), nullptr);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::Initialize failed: " RTC_LOG(LS_ERROR) << "IAudioClient::Initialize failed: "
<< ErrorToString(error); << ErrorToString(error);
return error.Error(); return error.Error();
@ -774,7 +942,7 @@ HRESULT SharedModeInitialize(IAudioClient* client,
// IAudioClient::SetEventHandle. // IAudioClient::SetEventHandle.
if (use_event) { if (use_event) {
error = client->SetEventHandle(event_handle); error = client->SetEventHandle(event_handle);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::SetEventHandle failed: " RTC_LOG(LS_ERROR) << "IAudioClient::SetEventHandle failed: "
<< ErrorToString(error); << ErrorToString(error);
return error.Error(); return error.Error();
@ -784,8 +952,13 @@ HRESULT SharedModeInitialize(IAudioClient* client,
UINT32 buffer_size_in_frames = 0; UINT32 buffer_size_in_frames = 0;
// Retrieves the size (maximum capacity) of the endpoint buffer. The size is // Retrieves the size (maximum capacity) of the endpoint buffer. The size is
// expressed as the number of audio frames the buffer can hold. // expressed as the number of audio frames the buffer can hold.
// For rendering clients, the buffer length determines the maximum amount of
// rendering data that the application can write to the endpoint buffer
// during a single processing pass. For capture clients, the buffer length
// determines the maximum amount of capture data that the audio engine can
// read from the endpoint buffer during a single processing pass.
error = client->GetBufferSize(&buffer_size_in_frames); error = client->GetBufferSize(&buffer_size_in_frames);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: "
<< ErrorToString(error); << ErrorToString(error);
return error.Error(); return error.Error();
@ -794,6 +967,10 @@ HRESULT SharedModeInitialize(IAudioClient* client,
*endpoint_buffer_size = buffer_size_in_frames; *endpoint_buffer_size = buffer_size_in_frames;
RTC_DLOG(INFO) << "endpoint buffer size: " << buffer_size_in_frames RTC_DLOG(INFO) << "endpoint buffer size: " << buffer_size_in_frames
<< " [audio frames]"; << " [audio frames]";
const double size_in_ms = static_cast<double>(buffer_size_in_frames) /
(format->Format.nSamplesPerSec / 1000.0);
RTC_DLOG(INFO) << "endpoint buffer size: "
<< static_cast<int>(size_in_ms + 0.5) << " [ms]";
RTC_DLOG(INFO) << "bytes per audio frame: " << format->Format.nBlockAlign; RTC_DLOG(INFO) << "bytes per audio frame: " << format->Format.nBlockAlign;
RTC_DLOG(INFO) << "endpoint buffer size: " RTC_DLOG(INFO) << "endpoint buffer size: "
<< buffer_size_in_frames * format->Format.nChannels * << buffer_size_in_frames * format->Format.nChannels *
@ -808,6 +985,92 @@ HRESULT SharedModeInitialize(IAudioClient* client,
return error.Error(); return error.Error();
} }
HRESULT SharedModeInitializeLowLatency(IAudioClient3* client,
const WAVEFORMATEXTENSIBLE* format,
HANDLE event_handle,
uint32_t period_in_frames,
bool auto_convert_pcm,
uint32_t* endpoint_buffer_size) {
RTC_DLOG(INFO) << "SharedModeInitializeLowLatency: period_in_frames="
<< period_in_frames
<< ", auto_convert_pcm=" << auto_convert_pcm;
RTC_DCHECK(client);
RTC_DCHECK_GT(period_in_frames, 0);
if (auto_convert_pcm) {
RTC_DLOG(LS_WARNING) << "Sample rate converter is enabled";
}
// Define stream flags.
DWORD stream_flags = 0;
bool use_event =
(event_handle != nullptr && event_handle != INVALID_HANDLE_VALUE);
if (use_event) {
stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
RTC_DLOG(INFO) << "The stream is initialized to be event driven";
}
if (auto_convert_pcm) {
stream_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
stream_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
}
RTC_DLOG(INFO) << "stream_flags: 0x" << rtc::ToHex(stream_flags);
// Initialize the shared mode client for lowest possible latency.
// It is assumed that GetSharedModeEnginePeriod() has been used to query the
// smallest possible engine period and that it is given by |period_in_frames|.
_com_error error = client->InitializeSharedAudioStream(
stream_flags, period_in_frames,
reinterpret_cast<const WAVEFORMATEX*>(format), nullptr);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient3::InitializeSharedAudioStream failed: "
<< ErrorToString(error);
return error.Error();
}
// Set the event handle.
if (use_event) {
error = client->SetEventHandle(event_handle);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::SetEventHandle failed: "
<< ErrorToString(error);
return error.Error();
}
}
UINT32 buffer_size_in_frames = 0;
// Retrieve the size (maximum capacity) of the endpoint buffer.
error = client->GetBufferSize(&buffer_size_in_frames);
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: "
<< ErrorToString(error);
return error.Error();
}
*endpoint_buffer_size = buffer_size_in_frames;
RTC_DLOG(INFO) << "endpoint buffer size: " << buffer_size_in_frames
<< " [audio frames]";
const double size_in_ms = static_cast<double>(buffer_size_in_frames) /
(format->Format.nSamplesPerSec / 1000.0);
RTC_DLOG(INFO) << "endpoint buffer size: "
<< static_cast<int>(size_in_ms + 0.5) << " [ms]";
RTC_DLOG(INFO) << "bytes per audio frame: " << format->Format.nBlockAlign;
RTC_DLOG(INFO) << "endpoint buffer size: "
<< buffer_size_in_frames * format->Format.nChannels *
(format->Format.wBitsPerSample / 8)
<< " [bytes]";
// TODO(henrika): utilize when delay measurements are added.
REFERENCE_TIME latency = 0;
error = client->GetStreamLatency(&latency);
if (FAILED(error.Error())) {
RTC_LOG(LS_WARNING) << "IAudioClient::GetStreamLatency failed: "
<< ErrorToString(error);
} else {
RTC_DLOG(INFO) << "stream latency: "
<< ReferenceTimeToTimeDelta(latency).ms() << " [ms]";
}
return error.Error();
}
ComPtr<IAudioRenderClient> CreateRenderClient(IAudioClient* client) { ComPtr<IAudioRenderClient> CreateRenderClient(IAudioClient* client) {
RTC_DLOG(INFO) << "CreateRenderClient"; RTC_DLOG(INFO) << "CreateRenderClient";
RTC_DCHECK(client); RTC_DCHECK(client);
@ -815,7 +1078,7 @@ ComPtr<IAudioRenderClient> CreateRenderClient(IAudioClient* client) {
// enables us to write output data to a rendering endpoint buffer. // enables us to write output data to a rendering endpoint buffer.
ComPtr<IAudioRenderClient> audio_render_client; ComPtr<IAudioRenderClient> audio_render_client;
_com_error error = client->GetService(IID_PPV_ARGS(&audio_render_client)); _com_error error = client->GetService(IID_PPV_ARGS(&audio_render_client));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) RTC_LOG(LS_ERROR)
<< "IAudioClient::GetService(IID_IAudioRenderClient) failed: " << "IAudioClient::GetService(IID_IAudioRenderClient) failed: "
<< ErrorToString(error); << ErrorToString(error);
@ -831,7 +1094,7 @@ ComPtr<IAudioCaptureClient> CreateCaptureClient(IAudioClient* client) {
// enables us to read input data from a capturing endpoint buffer. // enables us to read input data from a capturing endpoint buffer.
ComPtr<IAudioCaptureClient> audio_capture_client; ComPtr<IAudioCaptureClient> audio_capture_client;
_com_error error = client->GetService(IID_PPV_ARGS(&audio_capture_client)); _com_error error = client->GetService(IID_PPV_ARGS(&audio_capture_client));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) RTC_LOG(LS_ERROR)
<< "IAudioClient::GetService(IID_IAudioCaptureClient) failed: " << "IAudioClient::GetService(IID_IAudioCaptureClient) failed: "
<< ErrorToString(error); << ErrorToString(error);
@ -847,7 +1110,7 @@ ComPtr<IAudioClock> CreateAudioClock(IAudioClient* client) {
// monitor a stream's data rate and the current position in the stream. // monitor a stream's data rate and the current position in the stream.
ComPtr<IAudioClock> audio_clock; ComPtr<IAudioClock> audio_clock;
_com_error error = client->GetService(IID_PPV_ARGS(&audio_clock)); _com_error error = client->GetService(IID_PPV_ARGS(&audio_clock));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetService(IID_IAudioClock) failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetService(IID_IAudioClock) failed: "
<< ErrorToString(error); << ErrorToString(error);
return ComPtr<IAudioClock>(); return ComPtr<IAudioClock>();
@ -855,6 +1118,19 @@ ComPtr<IAudioClock> CreateAudioClock(IAudioClient* client) {
return audio_clock; return audio_clock;
} }
ComPtr<IAudioSessionControl> CreateAudioSessionControl(IAudioClient* client) {
RTC_DLOG(INFO) << "CreateAudioSessionControl";
RTC_DCHECK(client);
ComPtr<IAudioSessionControl> audio_session_control;
_com_error error = client->GetService(IID_PPV_ARGS(&audio_session_control));
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetService(IID_IAudioControl) failed: "
<< ErrorToString(error);
return ComPtr<IAudioSessionControl>();
}
return audio_session_control;
}
ComPtr<ISimpleAudioVolume> CreateSimpleAudioVolume(IAudioClient* client) { ComPtr<ISimpleAudioVolume> CreateSimpleAudioVolume(IAudioClient* client) {
RTC_DLOG(INFO) << "CreateSimpleAudioVolume"; RTC_DLOG(INFO) << "CreateSimpleAudioVolume";
RTC_DCHECK(client); RTC_DCHECK(client);
@ -862,7 +1138,7 @@ ComPtr<ISimpleAudioVolume> CreateSimpleAudioVolume(IAudioClient* client) {
// client to control the master volume level of an audio session. // client to control the master volume level of an audio session.
ComPtr<ISimpleAudioVolume> simple_audio_volume; ComPtr<ISimpleAudioVolume> simple_audio_volume;
_com_error error = client->GetService(IID_PPV_ARGS(&simple_audio_volume)); _com_error error = client->GetService(IID_PPV_ARGS(&simple_audio_volume));
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) RTC_LOG(LS_ERROR)
<< "IAudioClient::GetService(IID_ISimpleAudioVolume) failed: " << "IAudioClient::GetService(IID_ISimpleAudioVolume) failed: "
<< ErrorToString(error); << ErrorToString(error);
@ -878,7 +1154,7 @@ bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
RTC_DCHECK(render_client); RTC_DCHECK(render_client);
UINT32 endpoint_buffer_size = 0; UINT32 endpoint_buffer_size = 0;
_com_error error = client->GetBufferSize(&endpoint_buffer_size); _com_error error = client->GetBufferSize(&endpoint_buffer_size);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: "
<< ErrorToString(error); << ErrorToString(error);
return false; return false;
@ -888,7 +1164,7 @@ bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
// Get number of audio frames that are queued up to play in the endpoint // Get number of audio frames that are queued up to play in the endpoint
// buffer. // buffer.
error = client->GetCurrentPadding(&num_queued_frames); error = client->GetCurrentPadding(&num_queued_frames);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: " RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: "
<< ErrorToString(error); << ErrorToString(error);
return false; return false;
@ -899,7 +1175,7 @@ bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
int num_frames_to_fill = endpoint_buffer_size - num_queued_frames; int num_frames_to_fill = endpoint_buffer_size - num_queued_frames;
RTC_DLOG(INFO) << "num_frames_to_fill: " << num_frames_to_fill; RTC_DLOG(INFO) << "num_frames_to_fill: " << num_frames_to_fill;
error = render_client->GetBuffer(num_frames_to_fill, &data); error = render_client->GetBuffer(num_frames_to_fill, &data);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: " RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: "
<< ErrorToString(error); << ErrorToString(error);
return false; return false;
@ -909,7 +1185,7 @@ bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
// explicitly write silence data to the rendering buffer. // explicitly write silence data to the rendering buffer.
error = render_client->ReleaseBuffer(num_frames_to_fill, error = render_client->ReleaseBuffer(num_frames_to_fill,
AUDCLNT_BUFFERFLAGS_SILENT); AUDCLNT_BUFFERFLAGS_SILENT);
if (error.Error() != S_OK) { if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: " RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: "
<< ErrorToString(error); << ErrorToString(error);
return false; return false;
@ -947,6 +1223,11 @@ webrtc::TimeDelta ReferenceTimeToTimeDelta(REFERENCE_TIME time) {
return webrtc::TimeDelta::us(0.1 * time + 0.5); return webrtc::TimeDelta::us(0.1 * time + 0.5);
} }
double FramesToMilliseconds(uint32_t num_frames, uint16_t sample_rate) {
// Convert the current period in frames into milliseconds.
return static_cast<double>(num_frames) / (sample_rate / 1000.0);
}
std::string ErrorToString(const _com_error& error) { std::string ErrorToString(const _com_error& error) {
char ss_buf[1024]; char ss_buf[1024];
rtc::SimpleStringBuilder ss(ss_buf); rtc::SimpleStringBuilder ss(ss_buf);

View File

@ -33,6 +33,7 @@ namespace webrtc {
namespace webrtc_win { namespace webrtc_win {
static const int64_t kNumMicrosecsPerSec = webrtc::TimeDelta::seconds(1).us(); static const int64_t kNumMicrosecsPerSec = webrtc::TimeDelta::seconds(1).us();
static const int64_t kNumMillisecsPerSec = webrtc::TimeDelta::seconds(1).ms();
// Utility class which registers a thread with MMCSS in the constructor and // Utility class which registers a thread with MMCSS in the constructor and
// deregisters MMCSS in the destructor. The task name is given by |task_name|. // deregisters MMCSS in the destructor. The task name is given by |task_name|.
@ -42,6 +43,47 @@ static const int64_t kNumMicrosecsPerSec = webrtc::TimeDelta::seconds(1).us();
// lower-priority applications. // lower-priority applications.
class ScopedMMCSSRegistration { class ScopedMMCSSRegistration {
public: public:
const char* PriorityClassToString(DWORD priority_class) {
switch (priority_class) {
case ABOVE_NORMAL_PRIORITY_CLASS:
return "ABOVE_NORMAL";
case BELOW_NORMAL_PRIORITY_CLASS:
return "BELOW_NORMAL";
case HIGH_PRIORITY_CLASS:
return "HIGH";
case IDLE_PRIORITY_CLASS:
return "IDLE";
case NORMAL_PRIORITY_CLASS:
return "NORMAL";
case REALTIME_PRIORITY_CLASS:
return "REALTIME";
default:
return "INVALID";
}
}
const char* PriorityToString(int priority) {
switch (priority) {
case THREAD_PRIORITY_ABOVE_NORMAL:
return "ABOVE_NORMAL";
case THREAD_PRIORITY_BELOW_NORMAL:
return "BELOW_NORMAL";
case THREAD_PRIORITY_HIGHEST:
return "HIGHEST";
case THREAD_PRIORITY_IDLE:
return "IDLE";
case THREAD_PRIORITY_LOWEST:
return "LOWEST";
case THREAD_PRIORITY_NORMAL:
return "NORMAL";
case THREAD_PRIORITY_TIME_CRITICAL:
return "TIME_CRITICAL";
default:
// Can happen in combination with REALTIME_PRIORITY_CLASS.
return "INVALID";
}
}
explicit ScopedMMCSSRegistration(const TCHAR* task_name) { explicit ScopedMMCSSRegistration(const TCHAR* task_name) {
RTC_DLOG(INFO) << "ScopedMMCSSRegistration: " << rtc::ToUtf8(task_name); RTC_DLOG(INFO) << "ScopedMMCSSRegistration: " << rtc::ToUtf8(task_name);
// Register the calling thread with MMCSS for the supplied |task_name|. // Register the calling thread with MMCSS for the supplied |task_name|.
@ -50,6 +92,14 @@ class ScopedMMCSSRegistration {
if (mmcss_handle_ == nullptr) { if (mmcss_handle_ == nullptr) {
RTC_LOG(LS_ERROR) << "Failed to enable MMCSS on this thread: " RTC_LOG(LS_ERROR) << "Failed to enable MMCSS on this thread: "
<< GetLastError(); << GetLastError();
} else {
const DWORD priority_class = GetPriorityClass(GetCurrentProcess());
const int priority = GetThreadPriority(GetCurrentThread());
RTC_DLOG(INFO) << "priority class: "
<< PriorityClassToString(priority_class) << "("
<< priority_class << ")";
RTC_DLOG(INFO) << "priority: " << PriorityToString(priority) << "("
<< priority << ")";
} }
} }
@ -299,6 +349,11 @@ bool IsMMCSSSupported();
// devices. // devices.
int NumberOfActiveDevices(EDataFlow data_flow); int NumberOfActiveDevices(EDataFlow data_flow);
// Returns 1, 2, or 3 depending on what version of IAudioClient the platform
// supports.
// Example: IAudioClient2 is supported on Windows 8 and higher => 2 is returned.
uint32_t GetAudioClientVersion();
// Creates an IMMDeviceEnumerator interface which provides methods for // Creates an IMMDeviceEnumerator interface which provides methods for
// enumerating audio endpoint devices. // enumerating audio endpoint devices.
// TODO(henrika): IMMDeviceEnumerator::RegisterEndpointNotificationCallback. // TODO(henrika): IMMDeviceEnumerator::RegisterEndpointNotificationCallback.
@ -367,16 +422,29 @@ int NumberOfActiveSessions(IMMDevice* device);
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,
ERole role); ERole role);
Microsoft::WRL::ComPtr<IAudioClient2> Microsoft::WRL::ComPtr<IAudioClient2>
CreateClient2(const std::string& device_id, EDataFlow data_flow, ERole role); CreateClient2(const std::string& device_id, EDataFlow data_flow, ERole role);
Microsoft::WRL::ComPtr<IAudioClient3>
CreateClient3(const std::string& device_id, EDataFlow data_flow, ERole role);
// Sets the AudioCategory_Communications category. Should be called before // Sets the AudioCategory_Communications category. Should be called before
// GetSharedModeMixFormat() and IsFormatSupported(). // GetSharedModeMixFormat() and IsFormatSupported(). The |client| argument must
// Minimum supported client: Windows 8. // be an IAudioClient2 or IAudioClient3 interface pointer, hence only supported
// on Windows 8 and above.
// TODO(henrika): evaluate effect (if any). // TODO(henrika): evaluate effect (if any).
HRESULT SetClientProperties(IAudioClient2* client); HRESULT SetClientProperties(IAudioClient2* client);
// Returns the buffer size limits of the hardware audio engine in
// 100-nanosecond units given a specified |format|. Does not require prior
// audio stream initialization. The |client| argument must be an IAudioClient2
// or IAudioClient3 interface pointer, hence only supported on Windows 8 and
// above.
// TODO(henrika): always fails with AUDCLNT_E_OFFLOAD_MODE_ONLY.
HRESULT GetBufferSizeLimits(IAudioClient2* client,
const WAVEFORMATEXTENSIBLE* format,
REFERENCE_TIME* min_buffer_duration,
REFERENCE_TIME* max_buffer_duration);
// Get the mix format that the audio engine uses internally for processing // Get the mix format that the audio engine uses internally for processing
// of shared-mode streams. The client can call this method before calling // of shared-mode streams. The client can call this method before calling
// IAudioClient::Initialize. When creating a shared-mode stream for an audio // IAudioClient::Initialize. When creating a shared-mode stream for an audio
@ -403,15 +471,35 @@ HRESULT GetDevicePeriod(IAudioClient* client,
AUDCLNT_SHAREMODE share_mode, AUDCLNT_SHAREMODE share_mode,
REFERENCE_TIME* device_period); REFERENCE_TIME* device_period);
// Get the preferred audio parameters for the given |device_id|. The acquired // Returns the range of periodicities supported by the engine for the specified
// values should only be utilized for shared mode streamed since there are no // stream |format|. The periodicity of the engine is the rate at which the
// preferred settings for an exclusive mode stream. // engine wakes an event-driven audio client to transfer audio data to or from
// the engine. Can be used for low-latency support on some devices.
// The |client| argument must be an IAudioClient3 interface pointer, hence only
// supported on Windows 10 and above.
HRESULT GetSharedModeEnginePeriod(IAudioClient3* client3,
const WAVEFORMATEXTENSIBLE* format,
uint32_t* default_period_in_frames,
uint32_t* fundamental_period_in_frames,
uint32_t* min_period_in_frames,
uint32_t* max_period_in_frames);
// Get the preferred audio parameters for the given |device_id| or |client|
// corresponding to the stream format that the audio engine uses for its
// internal processing of shared-mode streams. The acquired values should only
// be utilized for shared mode streamed since there are no preferred settings
// for an exclusive mode stream.
HRESULT GetPreferredAudioParameters(const std::string& device_id, HRESULT GetPreferredAudioParameters(const std::string& device_id,
bool is_output_device, bool is_output_device,
webrtc::AudioParameters* params); webrtc::AudioParameters* params);
HRESULT GetPreferredAudioParameters(IAudioClient* client, HRESULT GetPreferredAudioParameters(IAudioClient* client,
webrtc::AudioParameters* params); webrtc::AudioParameters* params);
// As above but override the preferred sample rate and use |sample_rate|
// instead. Intended mainly for testing purposes and in combination with rate
// conversion.
HRESULT GetPreferredAudioParameters(IAudioClient* client,
webrtc::AudioParameters* params,
uint32_t sample_rate);
// After activating an IAudioClient interface on an audio endpoint device, // After activating an IAudioClient interface on an audio endpoint device,
// the client must initialize it once, and only once, to initialize the audio // the client must initialize it once, and only once, to initialize the audio
@ -419,19 +507,41 @@ HRESULT GetPreferredAudioParameters(IAudioClient* client,
// connects indirectly through the audio engine which does the mixing. // connects indirectly through the audio engine which does the mixing.
// If a valid event is provided in |event_handle|, the client will be // If a valid event is provided in |event_handle|, the client will be
// initialized for event-driven buffer handling. If |event_handle| is set to // initialized for event-driven buffer handling. If |event_handle| is set to
// nullptr, event-driven buffer handling is not utilized. // nullptr, event-driven buffer handling is not utilized. To achieve the
// minimum stream latency between the client application and audio endpoint
// device, set |buffer_duration| to 0. A client has the option of requesting a
// buffer size that is larger than what is strictly necessary to make timing
// glitches rare or nonexistent. Increasing the buffer size does not necessarily
// increase the stream latency. Each unit of reference time is 100 nanoseconds.
// The |auto_convert_pcm| parameter can be used for testing purposes to ensure
// that the sample rate of the client side does not have to match the audio
// engine mix format. If |auto_convert_pcm| is set to true, a rate converter
// will be inserted to convert between the sample rate in |format| and the
// preferred rate given by GetPreferredAudioParameters().
// The output parameter |endpoint_buffer_size| contains the size of the // The output parameter |endpoint_buffer_size| contains the size of the
// endpoint buffer and it is expressed as the number of audio frames the // endpoint buffer and it is expressed as the number of audio frames the
// buffer can hold. // buffer can hold.
// TODO(henrika):
// - use IAudioClient2::SetClientProperties before calling this method
// - IAudioClient::Initialize(your_format, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
// AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY)
HRESULT SharedModeInitialize(IAudioClient* client, HRESULT SharedModeInitialize(IAudioClient* client,
const WAVEFORMATEXTENSIBLE* format, const WAVEFORMATEXTENSIBLE* format,
HANDLE event_handle, HANDLE event_handle,
REFERENCE_TIME buffer_duration,
bool auto_convert_pcm,
uint32_t* endpoint_buffer_size); uint32_t* endpoint_buffer_size);
// Works as SharedModeInitialize() but adds support for using smaller engine
// periods than the default period.
// The |client| argument must be an IAudioClient3 interface pointer, hence only
// supported on Windows 10 and above.
// TODO(henrika): can probably be merged into SharedModeInitialize() to avoid
// duplicating code. Keeping as separate method for now until decided if we
// need low-latency support.
HRESULT SharedModeInitializeLowLatency(IAudioClient3* client,
const WAVEFORMATEXTENSIBLE* format,
HANDLE event_handle,
uint32_t period_in_frames,
bool auto_convert_pcm,
uint32_t* endpoint_buffer_size);
// Creates an IAudioRenderClient client for an existing IAudioClient given by // Creates an IAudioRenderClient client for an existing IAudioClient given by
// |client|. The IAudioRenderClient interface enables a client to write // |client|. The IAudioRenderClient interface enables a client to write
// output data to a rendering endpoint buffer. The methods in this interface // output data to a rendering endpoint buffer. The methods in this interface
@ -451,6 +561,12 @@ 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 AudioSessionControl interface for an existing IAudioClient given
// by |client|. The IAudioControl interface enables a client to configure the
// control parameters for an audio session and to monitor events in the session.
Microsoft::WRL::ComPtr<IAudioSessionControl> CreateAudioSessionControl(
IAudioClient* client);
// Creates an ISimpleAudioVolume interface for an existing IAudioClient given by // Creates an ISimpleAudioVolume interface for an existing IAudioClient given by
// |client|. This interface enables a client to control the master volume level // |client|. This interface enables a client to control the master volume level
// of an active audio session. // of an active audio session.
@ -469,6 +585,10 @@ std::string WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format);
// generic webrtc::TimeDelta which then can be converted to any time unit. // generic webrtc::TimeDelta which then can be converted to any time unit.
webrtc::TimeDelta ReferenceTimeToTimeDelta(REFERENCE_TIME time); webrtc::TimeDelta ReferenceTimeToTimeDelta(REFERENCE_TIME time);
// Converts size expressed in number of audio frames, |num_frames|, into
// milliseconds given a specified |sample_rate|.
double FramesToMilliseconds(uint32_t num_frames, uint16_t sample_rate);
// Converts a COM error into a human-readable string. // Converts a COM error into a human-readable string.
std::string ErrorToString(const _com_error& error); std::string ErrorToString(const _com_error& error);

View File

@ -91,6 +91,12 @@ TEST_F(CoreAudioUtilityWinTest, NumberOfActiveDevices) {
EXPECT_EQ(total_devices, render_devices + capture_devices); EXPECT_EQ(total_devices, render_devices + capture_devices);
} }
TEST_F(CoreAudioUtilityWinTest, GetAudioClientVersion) {
uint32_t client_version = core_audio_utility::GetAudioClientVersion();
EXPECT_GE(client_version, 1u);
EXPECT_LE(client_version, 3u);
}
TEST_F(CoreAudioUtilityWinTest, CreateDeviceEnumerator) { TEST_F(CoreAudioUtilityWinTest, CreateDeviceEnumerator) {
ABORT_TEST_IF_NOT(DevicesAvailable()); ABORT_TEST_IF_NOT(DevicesAvailable());
ComPtr<IMMDeviceEnumerator> enumerator = ComPtr<IMMDeviceEnumerator> enumerator =
@ -338,27 +344,90 @@ TEST_F(CoreAudioUtilityWinTest, CreateClient) {
TEST_F(CoreAudioUtilityWinTest, CreateClient2) { TEST_F(CoreAudioUtilityWinTest, CreateClient2) {
ABORT_TEST_IF_NOT(DevicesAvailable() && ABORT_TEST_IF_NOT(DevicesAvailable() &&
rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN10); core_audio_utility::GetAudioClientVersion() >= 2);
EDataFlow data_flow[] = {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_flow); ++i) { for (size_t i = 0; i < arraysize(data_flow); ++i) {
ComPtr<IAudioClient2> client = core_audio_utility::CreateClient2( ComPtr<IAudioClient2> client2 = core_audio_utility::CreateClient2(
AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole);
EXPECT_TRUE(client.Get()); EXPECT_TRUE(client2.Get());
}
}
TEST_F(CoreAudioUtilityWinTest, CreateClient3) {
ABORT_TEST_IF_NOT(DevicesAvailable() &&
core_audio_utility::GetAudioClientVersion() >= 3);
EDataFlow data_flow[] = {eRender, eCapture};
// Obtain reference to an IAudioClient3 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<IAudioClient3> client3 = core_audio_utility::CreateClient3(
AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole);
EXPECT_TRUE(client3.Get());
} }
} }
TEST_F(CoreAudioUtilityWinTest, SetClientProperties) { TEST_F(CoreAudioUtilityWinTest, SetClientProperties) {
ABORT_TEST_IF_NOT(DevicesAvailable()); ABORT_TEST_IF_NOT(DevicesAvailable() &&
core_audio_utility::GetAudioClientVersion() >= 2);
ComPtr<IAudioClient2> client = core_audio_utility::CreateClient2( ComPtr<IAudioClient2> client2 = core_audio_utility::CreateClient2(
AudioDeviceName::kDefaultDeviceId, eRender, eConsole); AudioDeviceName::kDefaultDeviceId, eRender, eConsole);
EXPECT_TRUE(client.Get()); EXPECT_TRUE(client2.Get());
EXPECT_TRUE(
SUCCEEDED(core_audio_utility::SetClientProperties(client2.Get())));
EXPECT_TRUE(SUCCEEDED(core_audio_utility::SetClientProperties(client.Get()))); ComPtr<IAudioClient3> client3 = core_audio_utility::CreateClient3(
AudioDeviceName::kDefaultDeviceId, eRender, eConsole);
EXPECT_TRUE(client3.Get());
EXPECT_TRUE(
SUCCEEDED(core_audio_utility::SetClientProperties(client3.Get())));
}
TEST_F(CoreAudioUtilityWinTest, GetSharedModeEnginePeriod) {
ABORT_TEST_IF_NOT(DevicesAvailable() &&
core_audio_utility::GetAudioClientVersion() >= 3);
ComPtr<IAudioClient3> client3 = core_audio_utility::CreateClient3(
AudioDeviceName::kDefaultDeviceId, eRender, eConsole);
EXPECT_TRUE(client3.Get());
WAVEFORMATPCMEX format;
EXPECT_TRUE(SUCCEEDED(
core_audio_utility::GetSharedModeMixFormat(client3.Get(), &format)));
uint32_t default_period = 0;
uint32_t fundamental_period = 0;
uint32_t min_period = 0;
uint32_t max_period = 0;
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetSharedModeEnginePeriod(
client3.Get(), &format, &default_period, &fundamental_period, &min_period,
&max_period)));
}
// TODO(henrika): figure out why usage of this API always reports
// AUDCLNT_E_OFFLOAD_MODE_ONLY.
TEST_F(CoreAudioUtilityWinTest, DISABLED_GetBufferSizeLimits) {
ABORT_TEST_IF_NOT(DevicesAvailable() &&
core_audio_utility::GetAudioClientVersion() >= 2);
ComPtr<IAudioClient2> client2 = core_audio_utility::CreateClient2(
AudioDeviceName::kDefaultDeviceId, eRender, eConsole);
EXPECT_TRUE(client2.Get());
WAVEFORMATPCMEX format;
EXPECT_TRUE(SUCCEEDED(
core_audio_utility::GetSharedModeMixFormat(client2.Get(), &format)));
REFERENCE_TIME min_buffer_duration = 0;
REFERENCE_TIME max_buffer_duration = 0;
EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetBufferSizeLimits(
client2.Get(), &format, &min_buffer_duration, &max_buffer_duration)));
} }
TEST_F(CoreAudioUtilityWinTest, GetSharedModeMixFormat) { TEST_F(CoreAudioUtilityWinTest, GetSharedModeMixFormat) {
@ -468,13 +537,13 @@ TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) {
// Perform a shared-mode initialization without event-driven buffer handling. // Perform a shared-mode initialization without event-driven buffer handling.
uint32_t endpoint_buffer_size = 0; uint32_t endpoint_buffer_size = 0;
HRESULT hr = core_audio_utility::SharedModeInitialize( HRESULT hr = core_audio_utility::SharedModeInitialize(
client.Get(), &format, nullptr, &endpoint_buffer_size); client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size);
EXPECT_TRUE(SUCCEEDED(hr)); EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_GT(endpoint_buffer_size, 0u); EXPECT_GT(endpoint_buffer_size, 0u);
// It is only possible to create a client once. // It is only possible to create a client once.
hr = core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, hr = core_audio_utility::SharedModeInitialize(
&endpoint_buffer_size); client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size);
EXPECT_FALSE(SUCCEEDED(hr)); EXPECT_FALSE(SUCCEEDED(hr));
EXPECT_EQ(hr, AUDCLNT_E_ALREADY_INITIALIZED); EXPECT_EQ(hr, AUDCLNT_E_ALREADY_INITIALIZED);
@ -483,8 +552,8 @@ TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) {
client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId,
eRender, eConsole); eRender, eConsole);
EXPECT_TRUE(client.Get()); EXPECT_TRUE(client.Get());
hr = core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, hr = core_audio_utility::SharedModeInitialize(
&endpoint_buffer_size); client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size);
EXPECT_TRUE(SUCCEEDED(hr)); EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_GT(endpoint_buffer_size, 0u); EXPECT_GT(endpoint_buffer_size, 0u);
@ -497,8 +566,8 @@ TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) {
format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1; format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1;
EXPECT_FALSE(core_audio_utility::IsFormatSupported( EXPECT_FALSE(core_audio_utility::IsFormatSupported(
client.Get(), AUDCLNT_SHAREMODE_SHARED, &format)); client.Get(), AUDCLNT_SHAREMODE_SHARED, &format));
hr = core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, hr = core_audio_utility::SharedModeInitialize(
&endpoint_buffer_size); client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size);
EXPECT_TRUE(FAILED(hr)); EXPECT_TRUE(FAILED(hr));
EXPECT_EQ(hr, E_INVALIDARG); EXPECT_EQ(hr, E_INVALIDARG);
@ -515,9 +584,12 @@ TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) {
EXPECT_TRUE(core_audio_utility::IsFormatSupported( EXPECT_TRUE(core_audio_utility::IsFormatSupported(
client.Get(), AUDCLNT_SHAREMODE_SHARED, &format)); client.Get(), AUDCLNT_SHAREMODE_SHARED, &format));
hr = core_audio_utility::SharedModeInitialize( hr = core_audio_utility::SharedModeInitialize(
client.Get(), &format, event_handle, &endpoint_buffer_size); client.Get(), &format, event_handle, 0, false, &endpoint_buffer_size);
EXPECT_TRUE(SUCCEEDED(hr)); EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_GT(endpoint_buffer_size, 0u); EXPECT_GT(endpoint_buffer_size, 0u);
// TODO(henrika): possibly add test for signature which overrides the default
// sample rate.
} }
TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) { TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
@ -547,7 +619,7 @@ TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
// Do a proper initialization and verify that it works this time. // Do a proper initialization and verify that it works this time.
core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr,
&endpoint_buffer_size); 0, false, &endpoint_buffer_size);
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);
@ -559,7 +631,7 @@ TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) {
// Do a proper initialization and verify that it works this time. // Do a proper initialization and verify that it works this time.
core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr,
&endpoint_buffer_size); 0, false, &endpoint_buffer_size);
capture_client = core_audio_utility::CreateCaptureClient(client.Get()); capture_client = core_audio_utility::CreateCaptureClient(client.Get());
EXPECT_TRUE(capture_client.Get()); EXPECT_TRUE(capture_client.Get());
EXPECT_GT(endpoint_buffer_size, 0u); EXPECT_GT(endpoint_buffer_size, 0u);
@ -592,8 +664,8 @@ TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) {
EXPECT_FALSE(audio_clock.Get()); EXPECT_FALSE(audio_clock.Get());
// Do a proper initialization and verify that it works this time. // Do a proper initialization and verify that it works this time.
core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0,
&endpoint_buffer_size); false, &endpoint_buffer_size);
audio_clock = core_audio_utility::CreateAudioClock(client.Get()); audio_clock = core_audio_utility::CreateAudioClock(client.Get());
EXPECT_TRUE(audio_clock.Get()); EXPECT_TRUE(audio_clock.Get());
EXPECT_GT(endpoint_buffer_size, 0u); EXPECT_GT(endpoint_buffer_size, 0u);
@ -605,6 +677,51 @@ TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) {
} }
} }
TEST_F(CoreAudioUtilityWinTest, CreateAudioSessionControl) {
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<IAudioSessionControl> audio_session_control;
// 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 session control using an
// unitialized client interface.
audio_session_control =
core_audio_utility::CreateAudioSessionControl(client.Get());
EXPECT_FALSE(audio_session_control.Get());
// Do a proper initialization and verify that it works this time.
core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0,
false, &endpoint_buffer_size);
audio_session_control =
core_audio_utility::CreateAudioSessionControl(client.Get());
EXPECT_TRUE(audio_session_control.Get());
EXPECT_GT(endpoint_buffer_size, 0u);
// Use the audio session control and verify that the session state can be
// queried. When a client opens a session by assigning the first stream to
// the session (by calling the IAudioClient::Initialize method), the initial
// session state is inactive. The session state changes from inactive to
// active when a stream in the session begins running (because the client
// has called the IAudioClient::Start method).
AudioSessionState state;
EXPECT_TRUE(SUCCEEDED(audio_session_control->GetState(&state)));
EXPECT_EQ(state, AudioSessionStateInactive);
}
}
TEST_F(CoreAudioUtilityWinTest, CreateSimpleAudioVolume) { TEST_F(CoreAudioUtilityWinTest, CreateSimpleAudioVolume) {
ABORT_TEST_IF_NOT(DevicesAvailable()); ABORT_TEST_IF_NOT(DevicesAvailable());
@ -631,8 +748,8 @@ TEST_F(CoreAudioUtilityWinTest, CreateSimpleAudioVolume) {
EXPECT_FALSE(simple_audio_volume.Get()); EXPECT_FALSE(simple_audio_volume.Get());
// Do a proper initialization and verify that it works this time. // Do a proper initialization and verify that it works this time.
core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0,
&endpoint_buffer_size); false, &endpoint_buffer_size);
simple_audio_volume = simple_audio_volume =
core_audio_utility::CreateSimpleAudioVolume(client.Get()); core_audio_utility::CreateSimpleAudioVolume(client.Get());
EXPECT_TRUE(simple_audio_volume.Get()); EXPECT_TRUE(simple_audio_volume.Get());
@ -666,8 +783,8 @@ TEST_F(CoreAudioUtilityWinTest, FillRenderEndpointBufferWithSilence) {
uint32_t endpoint_buffer_size = 0; uint32_t endpoint_buffer_size = 0;
EXPECT_TRUE(SUCCEEDED( EXPECT_TRUE(SUCCEEDED(
core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); core_audio_utility::GetSharedModeMixFormat(client.Get(), &format)));
core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0,
&endpoint_buffer_size); false, &endpoint_buffer_size);
EXPECT_GT(endpoint_buffer_size, 0u); EXPECT_GT(endpoint_buffer_size, 0u);
ComPtr<IAudioRenderClient> render_client( ComPtr<IAudioRenderClient> render_client(

View File

@ -261,6 +261,12 @@ OSInfo::OSInfo()
architecture_(OTHER_ARCHITECTURE), architecture_(OTHER_ARCHITECTURE),
wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) { wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) {
OSVERSIONINFOEX version_info = {sizeof version_info}; OSVERSIONINFOEX version_info = {sizeof version_info};
// Applications not manifested for Windows 8.1 or Windows 10 will return the
// Windows 8 OS version value (6.2). Once an application is manifested for a
// given operating system version, GetVersionEx() will always return the
// version that the application is manifested for in future releases.
// https://docs.microsoft.com/en-us/windows/desktop/SysInfo/targeting-your-application-at-windows-8-1.
// https://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe.
::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info)); ::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info));
version_number_.major = version_info.dwMajorVersion; version_number_.major = version_info.dwMajorVersion;
version_number_.minor = version_info.dwMinorVersion; version_number_.minor = version_info.dwMinorVersion;