diff --git a/modules/audio_device/audio_device_unittest.cc b/modules/audio_device/audio_device_unittest.cc index 1b970d5dad..0479a0b2e2 100644 --- a/modules/audio_device/audio_device_unittest.cc +++ b/modules/audio_device/audio_device_unittest.cc @@ -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. diff --git a/modules/audio_device/win/core_audio_base_win.cc b/modules/audio_device/win/core_audio_base_win.cc index 56abe85e64..c7887cad72 100644 --- a/modules/audio_device/win/core_audio_base_win.cc +++ b/modules/audio_device/win/core_audio_base_win.cc @@ -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 diff --git a/modules/audio_device/win/core_audio_utility_win.cc b/modules/audio_device/win/core_audio_utility_win.cc index 85234ac142..1f60e7618f 100644 --- a/modules/audio_device/win/core_audio_utility_win.cc +++ b/modules/audio_device/win/core_audio_utility_win.cc @@ -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(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 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 mix_format; _com_error error = - client->GetMixFormat(reinterpret_cast(&format_ex)); + client->GetMixFormat(reinterpret_cast(&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 closest_match; + ScopedCoMem 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(format), - reinterpret_cast(&closest_match)); + &closest_match); + RTC_LOG(INFO) << WaveFormatToString( + const_cast(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(); } diff --git a/modules/audio_device/win/core_audio_utility_win.h b/modules/audio_device/win/core_audio_utility_win.h index 6e2c85bf6f..5a27edb6d4 100644 --- a/modules/audio_device/win/core_audio_utility_win.h +++ b/modules/audio_device/win/core_audio_utility_win.h @@ -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(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 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. diff --git a/modules/audio_device/win/core_audio_utility_win_unittest.cc b/modules/audio_device/win/core_audio_utility_win_unittest.cc index 2d423feeb0..52b647dbe1 100644 --- a/modules/audio_device/win/core_audio_utility_win_unittest.cc +++ b/modules/audio_device/win/core_audio_utility_win_unittest.cc @@ -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) {