Tests that all available audio devices can be selected and used by the ADM.

New tests are:

- AudioDeviceTest.StartStopPlayoutWithRealDevice
- AudioDeviceTest.StartStopRecordingWithRealDevice

(the comments below only affects ADM2 on Windows):

When adding these tests it was found that we could hit the same known issue
as in https://bugs.chromium.org/p/chromium/issues/detail?id=803056 and the
same solution as in Chrome was therefore ported from Chrome to WebRTC.

Hence, this change also adds support for core_audio_utility::WaveFormatWrapper
to support devices that can return a format where only the WAVEFORMATEX parts is
initialized. The old version would only DCHECK for these devices and that could
lead to an unpredictable behavior.

Tbr: minyue
Bug: webrtc:11093
Change-Id: Icb238c5475100f251ce4e55e39a03653da04dbda
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/159982
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Commit-Queue: Henrik Andreassson <henrika@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29824}
This commit is contained in:
henrika
2019-11-18 17:36:08 +01:00
committed by Commit Bot
parent 8ae70f6a30
commit 351173c88c
5 changed files with 266 additions and 82 deletions

View File

@ -23,6 +23,7 @@
#include "api/task_queue/task_queue_factory.h"
#include "modules/audio_device/audio_device_impl.h"
#include "modules/audio_device/include/mock_audio_transport.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/buffer.h"
#include "rtc_base/critical_section.h"
#include "rtc_base/event.h"
@ -808,6 +809,60 @@ TEST_P(MAYBE_AudioDeviceTest, StartStopRecording) {
StopRecording();
}
// Tests Start/Stop playout for all available input devices to ensure that
// the selected device can be created and used as intended.
TEST_P(MAYBE_AudioDeviceTest, StartStopPlayoutWithRealDevice) {
SKIP_TEST_IF_NOT(requirements_satisfied());
int num_devices = audio_device()->PlayoutDevices();
if (NewWindowsAudioDeviceModuleIsUsed()) {
num_devices += 2;
}
EXPECT_GT(num_devices, 0);
// Verify that all available playout devices can be set and used.
for (int i = 0; i < num_devices; ++i) {
EXPECT_EQ(0, audio_device()->SetPlayoutDevice(i));
StartPlayout();
StopPlayout();
}
#ifdef WEBRTC_WIN
AudioDeviceModule::WindowsDeviceType device_role[] = {
AudioDeviceModule::kDefaultDevice,
AudioDeviceModule::kDefaultCommunicationDevice};
for (size_t i = 0; i < arraysize(device_role); ++i) {
EXPECT_EQ(0, audio_device()->SetPlayoutDevice(device_role[i]));
StartPlayout();
StopPlayout();
}
#endif
}
// Tests Start/Stop recording for all available input devices to ensure that
// the selected device can be created and used as intended.
TEST_P(MAYBE_AudioDeviceTest, StartStopRecordingWithRealDevice) {
SKIP_TEST_IF_NOT(requirements_satisfied());
int num_devices = audio_device()->RecordingDevices();
if (NewWindowsAudioDeviceModuleIsUsed()) {
num_devices += 2;
}
EXPECT_GT(num_devices, 0);
// Verify that all available recording devices can be set and used.
for (int i = 0; i < num_devices; ++i) {
EXPECT_EQ(0, audio_device()->SetRecordingDevice(i));
StartRecording();
StopRecording();
}
#ifdef WEBRTC_WIN
AudioDeviceModule::WindowsDeviceType device_role[] = {
AudioDeviceModule::kDefaultDevice,
AudioDeviceModule::kDefaultCommunicationDevice};
for (size_t i = 0; i < arraysize(device_role); ++i) {
EXPECT_EQ(0, audio_device()->SetRecordingDevice(device_role[i]));
StartRecording();
StopRecording();
}
#endif
}
// Tests Init/Stop/Init recording without any registered audio callback.
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=8041 for details
// on why this test is useful.

View File

@ -391,7 +391,7 @@ bool CoreAudioBase::Init() {
format_.dwChannelMask =
format->nChannels == 1 ? KSAUDIO_SPEAKER_MONO : KSAUDIO_SPEAKER_STEREO;
format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
RTC_DLOG(INFO) << core_audio_utility::WaveFormatExToString(&format_);
RTC_DLOG(INFO) << core_audio_utility::WaveFormatToString(&format_);
// Verify that the format is supported but exclude the test if the default
// sample rate has been overridden. If so, the WASAPI audio engine will do

View File

@ -157,50 +157,20 @@ std::string ChannelMaskToString(DWORD channel_mask) {
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
// Converts from channel mask to DirectSound speaker configuration.
// The values below are copied from ksmedia.h.
// Example: KSAUDIO_SPEAKER_STEREO = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT).
const char* DirectSoundConfigToString(DWORD channel_mask) {
switch (channel_mask) {
case KSAUDIO_SPEAKER_DIRECTOUT:
return "KSAUDIO_DIRECTOUT";
case KSAUDIO_SPEAKER_MONO:
// Front center (C)
return "KSAUDIO_MONO";
case KSAUDIO_SPEAKER_1POINT1:
return "KSAUDIO_1POINT1";
case KSAUDIO_SPEAKER_STEREO:
// Front left (L), front right (R).
return "KSAUDIO_STEREO";
case KSAUDIO_SPEAKER_2POINT1:
return "KSAUDIO_2POINT1";
case KSAUDIO_SPEAKER_3POINT0:
return "KSAUDIO_3POINT0";
case KSAUDIO_SPEAKER_3POINT1:
return "KSAUDIO_3POINT1";
case KSAUDIO_SPEAKER_QUAD:
// L, R, back left (Lb), back right (Rb).
return "KSAUDIO_QUAD";
case KSAUDIO_SPEAKER_SURROUND:
// L, R, front center (C), back center (Cb).
return "KSAUDIO_SURROUND";
case KSAUDIO_SPEAKER_5POINT0:
return "KSAUDIO_5POINT0";
case KSAUDIO_SPEAKER_5POINT1:
return "KSAUDIO_5POINT1";
case KSAUDIO_SPEAKER_7POINT0:
return "KSAUDIO_7POINT0";
case KSAUDIO_SPEAKER_7POINT1:
// L, R, C, Lb, Rb, front left-of-center, front right-of-center, LFE.
return "KSAUDIO_7POINT1";
case KSAUDIO_SPEAKER_5POINT1_SURROUND:
// L, R, C, side left (Ls), side right (Rs), LFE.
return "KSAUDIO_5POINT1_SURROUND";
case KSAUDIO_SPEAKER_7POINT1_SURROUND:
// L, R, C, Lb, Rb, Ls, Rs, LFE.
return "KSAUDIO_7POINT1_SURROUND";
// Converts the most common format tags defined in mmreg.h into string
// equivalents. Mainly intended for log messages.
const char* WaveFormatTagToString(WORD format_tag) {
switch (format_tag) {
case WAVE_FORMAT_UNKNOWN:
return "WAVE_FORMAT_UNKNOWN";
case WAVE_FORMAT_PCM:
return "WAVE_FORMAT_PCM";
case WAVE_FORMAT_IEEE_FLOAT:
return "WAVE_FORMAT_IEEE_FLOAT";
case WAVE_FORMAT_EXTENSIBLE:
return "WAVE_FORMAT_EXTENSIBLE";
default:
return "KSAUDIO_INVALID";
return "UNKNOWN";
}
}
@ -589,6 +559,31 @@ HRESULT GetPreferredAudioParametersInternal(IAudioClient* client,
namespace core_audio_utility {
// core_audio_utility::WaveFormatWrapper implementation.
WAVEFORMATEXTENSIBLE* WaveFormatWrapper::GetExtensible() const {
RTC_CHECK(IsExtensible());
return reinterpret_cast<WAVEFORMATEXTENSIBLE*>(ptr_);
}
bool WaveFormatWrapper::IsExtensible() const {
return ptr_->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ptr_->cbSize >= 22;
}
bool WaveFormatWrapper::IsPcm() const {
return IsExtensible() ? GetExtensible()->SubFormat == KSDATAFORMAT_SUBTYPE_PCM
: ptr_->wFormatTag == WAVE_FORMAT_PCM;
}
bool WaveFormatWrapper::IsFloat() const {
return IsExtensible()
? GetExtensible()->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
: ptr_->wFormatTag == WAVE_FORMAT_IEEE_FLOAT;
}
size_t WaveFormatWrapper::size() const {
return sizeof(*ptr_) + ptr_->cbSize;
}
bool IsSupported() {
RTC_DLOG(INFO) << "IsSupported";
static bool g_is_supported = IsSupportedInternal();
@ -904,19 +899,52 @@ HRESULT GetSharedModeMixFormat(IAudioClient* client,
WAVEFORMATEXTENSIBLE* format) {
RTC_DLOG(INFO) << "GetSharedModeMixFormat";
RTC_DCHECK(client);
ScopedCoMem<WAVEFORMATEXTENSIBLE> format_ex;
// The GetMixFormat method retrieves the stream format that the audio engine
// uses for its internal processing of shared-mode streams. The method
// allocates the storage for the structure and this memory will be released
// when |mix_format| goes out of scope. The GetMixFormat method retrieves a
// format descriptor that is in the form of a WAVEFORMATEXTENSIBLE structure
// instead of a standalone WAVEFORMATEX structure. The method outputs a
// pointer to the WAVEFORMATEX structure that is embedded at the start of
// this WAVEFORMATEXTENSIBLE structure.
// Note that, crbug/803056 indicates that some devices can return a format
// where only the WAVEFORMATEX parts is initialized and we must be able to
// account for that.
ScopedCoMem<WAVEFORMATEXTENSIBLE> mix_format;
_com_error error =
client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&format_ex));
client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&mix_format));
if (FAILED(error.Error())) {
RTC_LOG(LS_ERROR) << "IAudioClient::GetMixFormat failed: "
<< ErrorToString(error);
return error.Error();
}
size_t bytes = sizeof(WAVEFORMATEX) + format_ex->Format.cbSize;
RTC_DCHECK_EQ(bytes, sizeof(WAVEFORMATEXTENSIBLE));
memcpy(format, format_ex, bytes);
RTC_DLOG(INFO) << WaveFormatExToString(format);
// Use a wave format wrapper to make things simpler.
WaveFormatWrapper wrapped_format(mix_format.Get());
// Verify that the reported format can be mixed by the audio engine in
// shared mode.
if (!wrapped_format.IsPcm() && !wrapped_format.IsFloat()) {
RTC_DLOG(LS_ERROR)
<< "Only pure PCM or float audio streams can be mixed in shared mode";
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}
// Log a warning for the rare case where |mix_format| only contains a
// stand-alone WAVEFORMATEX structure but don't return.
if (!wrapped_format.IsExtensible()) {
RTC_DLOG(WARNING)
<< "The returned format contains no extended information. "
"The size is "
<< wrapped_format.size() << " bytes.";
}
// Copy the correct number of bytes into |*format| taking into account if
// the returned structure is correctly extended or not.
RTC_CHECK_LE(wrapped_format.size(), sizeof(WAVEFORMATEXTENSIBLE));
memcpy(format, wrapped_format.get(), wrapped_format.size());
RTC_DLOG(INFO) << WaveFormatToString(format);
return error.Error();
}
@ -926,7 +954,7 @@ bool IsFormatSupported(IAudioClient* client,
const WAVEFORMATEXTENSIBLE* format) {
RTC_DLOG(INFO) << "IsFormatSupported";
RTC_DCHECK(client);
ScopedCoMem<WAVEFORMATEXTENSIBLE> closest_match;
ScopedCoMem<WAVEFORMATEX> closest_match;
// This method provides a way for a client to determine, before calling
// IAudioClient::Initialize, whether the audio engine supports a particular
// stream format or not. In shared mode, the audio engine always supports
@ -934,7 +962,9 @@ bool IsFormatSupported(IAudioClient* client,
// TODO(henrika): verify support for exclusive mode as well?
_com_error error = client->IsFormatSupported(
share_mode, reinterpret_cast<const WAVEFORMATEX*>(format),
reinterpret_cast<WAVEFORMATEX**>(&closest_match));
&closest_match);
RTC_LOG(INFO) << WaveFormatToString(
const_cast<WAVEFORMATEXTENSIBLE*>(format));
if ((error.Error() == S_OK) && (closest_match == nullptr)) {
RTC_DLOG(INFO)
<< "The audio endpoint device supports the specified stream format";
@ -943,7 +973,7 @@ bool IsFormatSupported(IAudioClient* client,
// only be triggered for shared mode.
RTC_LOG(LS_WARNING)
<< "Exact format is not supported, but a closest match exists";
RTC_LOG(INFO) << WaveFormatExToString(closest_match);
RTC_LOG(INFO) << WaveFormatToString(closest_match.Get());
} else if ((error.Error() == AUDCLNT_E_UNSUPPORTED_FORMAT) &&
(closest_match == nullptr)) {
// The audio engine does not support the caller-specified format or any
@ -1381,31 +1411,34 @@ bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
return true;
}
std::string WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format) {
RTC_DCHECK_EQ(format->Format.wFormatTag, WAVE_FORMAT_EXTENSIBLE);
std::string WaveFormatToString(const WaveFormatWrapper format) {
char ss_buf[1024];
rtc::SimpleStringBuilder ss(ss_buf);
ss.AppendFormat("wFormatTag: WAVE_FORMAT_EXTENSIBLE");
ss.AppendFormat(", nChannels: %d", format->Format.nChannels);
ss.AppendFormat(", nSamplesPerSec: %d", format->Format.nSamplesPerSec);
ss.AppendFormat(", nAvgBytesPerSec: %d", format->Format.nAvgBytesPerSec);
ss.AppendFormat(", nBlockAlign: %d", format->Format.nBlockAlign);
ss.AppendFormat(", wBitsPerSample: %d", format->Format.wBitsPerSample);
ss.AppendFormat(", cbSize: %d", format->Format.cbSize);
ss.AppendFormat(", wValidBitsPerSample: %d",
format->Samples.wValidBitsPerSample);
ss.AppendFormat(", dwChannelMask: 0x%X", format->dwChannelMask);
if (format->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) {
ss << ", SubFormat: KSDATAFORMAT_SUBTYPE_PCM";
} else if (format->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
ss << ", SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT";
// Start with the WAVEFORMATEX part (which always exists).
ss.AppendFormat("wFormatTag: %s (0x%X)",
WaveFormatTagToString(format->wFormatTag),
format->wFormatTag);
ss.AppendFormat(", nChannels: %d", format->nChannels);
ss.AppendFormat(", nSamplesPerSec: %d", format->nSamplesPerSec);
ss.AppendFormat(", nAvgBytesPerSec: %d", format->nAvgBytesPerSec);
ss.AppendFormat(", nBlockAlign: %d", format->nBlockAlign);
ss.AppendFormat(", wBitsPerSample: %d", format->wBitsPerSample);
ss.AppendFormat(", cbSize: %d", format->cbSize);
if (!format.IsExtensible())
return ss.str();
// Append the WAVEFORMATEXTENSIBLE part (which we know exists).
ss.AppendFormat(
" [+] wValidBitsPerSample: %d, dwChannelMask: %s",
format.GetExtensible()->Samples.wValidBitsPerSample,
ChannelMaskToString(format.GetExtensible()->dwChannelMask).c_str());
if (format.IsPcm()) {
ss.AppendFormat("%s", ", SubFormat: KSDATAFORMAT_SUBTYPE_PCM");
} else if (format.IsFloat()) {
ss.AppendFormat("%s", ", SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT");
} else {
ss << ", SubFormat: NOT_SUPPORTED";
ss.AppendFormat("%s", ", SubFormat: NOT_SUPPORTED");
}
ss.AppendFormat("\nChannel configuration: %s",
ChannelMaskToString(format->dwChannelMask).c_str());
ss.AppendFormat("\nDirectSound configuration : %s",
DirectSoundConfigToString(format->dwChannelMask));
return ss.str();
}

View File

@ -327,6 +327,32 @@ class ScopedHandle {
// These methods are based on media::CoreAudioUtil in Chrome.
namespace core_audio_utility {
// Helper class which automates casting between WAVEFORMATEX and
// WAVEFORMATEXTENSIBLE raw pointers using implicit constructors and
// operator overloading. Note that, no memory is allocated by this utility
// structure. It only serves as a handle (or a wrapper) of the structure
// provided to it at construction.
class WaveFormatWrapper {
public:
WaveFormatWrapper(WAVEFORMATEXTENSIBLE* p)
: ptr_(reinterpret_cast<WAVEFORMATEX*>(p)) {}
WaveFormatWrapper(WAVEFORMATEX* p) : ptr_(p) {}
~WaveFormatWrapper() = default;
operator WAVEFORMATEX*() const { return ptr_; }
WAVEFORMATEX* operator->() const { return ptr_; }
WAVEFORMATEX* get() const { return ptr_; }
WAVEFORMATEXTENSIBLE* GetExtensible() const;
bool IsExtensible() const;
bool IsPcm() const;
bool IsFloat() const;
size_t size() const;
private:
WAVEFORMATEX* ptr_;
};
// Returns true if Windows Core Audio is supported.
// Always verify that this method returns true before using any of the
// other methods in this class.
@ -576,8 +602,10 @@ Microsoft::WRL::ComPtr<ISimpleAudioVolume> CreateSimpleAudioVolume(
// given by |render_client|.
bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
IAudioRenderClient* render_client);
// Transforms a WAVEFORMATEXTENSIBLE struct to a human-readable string.
std::string WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format);
// Prints/logs all fields of the format structure in |format|.
// Also supports extended versions (WAVEFORMATEXTENSIBLE).
std::string WaveFormatToString(const WaveFormatWrapper format);
// Converts Windows internal REFERENCE_TIME (100 nanosecond units) into
// generic webrtc::TimeDelta which then can be converted to any time unit.

View File

@ -81,6 +81,68 @@ class CoreAudioUtilityWinTest : public ::testing::Test {
ScopedCOMInitializer com_init_;
};
TEST_F(CoreAudioUtilityWinTest, WaveFormatWrapper) {
// Use default constructor for WAVEFORMATEX and verify its size.
WAVEFORMATEX format = {};
core_audio_utility::WaveFormatWrapper wave_format(&format);
EXPECT_FALSE(wave_format.IsExtensible());
EXPECT_EQ(wave_format.size(), sizeof(WAVEFORMATEX));
EXPECT_EQ(wave_format->cbSize, 0);
// Ensure that the stand-alone WAVEFORMATEX structure has a valid format tag
// and that all accessors work.
format.wFormatTag = WAVE_FORMAT_PCM;
EXPECT_FALSE(wave_format.IsExtensible());
EXPECT_EQ(wave_format.size(), sizeof(WAVEFORMATEX));
EXPECT_EQ(wave_format.get()->wFormatTag, WAVE_FORMAT_PCM);
EXPECT_EQ(wave_format->wFormatTag, WAVE_FORMAT_PCM);
// Next, ensure that the size is valid. Stand-alone is not extended.
EXPECT_EQ(wave_format.size(), sizeof(WAVEFORMATEX));
// Verify format types for the stand-alone version.
EXPECT_TRUE(wave_format.IsPcm());
EXPECT_FALSE(wave_format.IsFloat());
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
EXPECT_TRUE(wave_format.IsFloat());
}
TEST_F(CoreAudioUtilityWinTest, WaveFormatWrapperExtended) {
// Use default constructor for WAVEFORMATEXTENSIBLE and verify that it
// results in same size as for WAVEFORMATEX even if the size of |format_ex|
// equals the size of WAVEFORMATEXTENSIBLE.
WAVEFORMATEXTENSIBLE format_ex = {};
core_audio_utility::WaveFormatWrapper wave_format_ex(&format_ex);
EXPECT_FALSE(wave_format_ex.IsExtensible());
EXPECT_EQ(wave_format_ex.size(), sizeof(WAVEFORMATEX));
EXPECT_EQ(wave_format_ex->cbSize, 0);
// Ensure that the extended structure has a valid format tag and that all
// accessors work.
format_ex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
EXPECT_FALSE(wave_format_ex.IsExtensible());
EXPECT_EQ(wave_format_ex.size(), sizeof(WAVEFORMATEX));
EXPECT_EQ(wave_format_ex->wFormatTag, WAVE_FORMAT_EXTENSIBLE);
EXPECT_EQ(wave_format_ex.get()->wFormatTag, WAVE_FORMAT_EXTENSIBLE);
// Next, ensure that the size is valid (sum of stand-alone and extended).
// Now the structure qualifies as extended.
format_ex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
EXPECT_TRUE(wave_format_ex.IsExtensible());
EXPECT_EQ(wave_format_ex.size(), sizeof(WAVEFORMATEXTENSIBLE));
EXPECT_TRUE(wave_format_ex.GetExtensible());
EXPECT_EQ(wave_format_ex.GetExtensible()->Format.wFormatTag,
WAVE_FORMAT_EXTENSIBLE);
// Verify format types for the extended version.
EXPECT_FALSE(wave_format_ex.IsPcm());
format_ex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
EXPECT_TRUE(wave_format_ex.IsPcm());
EXPECT_FALSE(wave_format_ex.IsFloat());
format_ex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
EXPECT_TRUE(wave_format_ex.IsFloat());
}
TEST_F(CoreAudioUtilityWinTest, NumberOfActiveDevices) {
ABORT_TEST_IF_NOT(DevicesAvailable());
int render_devices = core_audio_utility::NumberOfActiveDevices(eRender);
@ -438,14 +500,20 @@ TEST_F(CoreAudioUtilityWinTest, GetSharedModeMixFormat) {
EXPECT_TRUE(client.Get());
// Perform a simple sanity test of the acquired format structure.
WAVEFORMATPCMEX format;
WAVEFORMATEXTENSIBLE format;
EXPECT_TRUE(SUCCEEDED(
core_audio_utility::GetSharedModeMixFormat(client.Get(), &format)));
EXPECT_GE(format.Format.nChannels, 1);
EXPECT_GE(format.Format.nSamplesPerSec, 8000u);
EXPECT_GE(format.Format.wBitsPerSample, 16);
EXPECT_GE(format.Samples.wValidBitsPerSample, 16);
EXPECT_EQ(format.Format.wFormatTag, WAVE_FORMAT_EXTENSIBLE);
core_audio_utility::WaveFormatWrapper wformat(&format);
EXPECT_GE(wformat->nChannels, 1);
EXPECT_GE(wformat->nSamplesPerSec, 8000u);
EXPECT_GE(wformat->wBitsPerSample, 16);
if (wformat.IsExtensible()) {
EXPECT_EQ(wformat->wFormatTag, WAVE_FORMAT_EXTENSIBLE);
EXPECT_GE(wformat->cbSize, 22);
EXPECT_GE(wformat.GetExtensible()->Samples.wValidBitsPerSample, 16);
} else {
EXPECT_EQ(wformat->cbSize, 0);
}
}
TEST_F(CoreAudioUtilityWinTest, IsFormatSupported) {