diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-06 21:07:52 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-06 21:07:52 +0000 |
commit | 627e7bc136622483f09138505a2c695194ffea0a (patch) | |
tree | a25b314f6c04ce3f2bb1a694f5e74e750150fa1c /media/audio/win | |
parent | 16969f048808af3157a8d75bee3cd285a5527f7e (diff) | |
download | chromium_src-627e7bc136622483f09138505a2c695194ffea0a.zip chromium_src-627e7bc136622483f09138505a2c695194ffea0a.tar.gz chromium_src-627e7bc136622483f09138505a2c695194ffea0a.tar.bz2 |
Adds input volume control support for Windows platforms.
This CL also adds a device_id input parameter to media::GetAudioInputHardwareSampleRate() and media::GetAudioInputHardwareChannelCount(). We need this new flexibility to be able to perform unit tests where the volume is modified for all supported devices. Without it, we will not be able to open the audio-input stream using the correct sample rate and channels count for all devices since all we can get information about is the default device.
BUG=115013
TEST=media_unittests
Review URL: http://codereview.chromium.org/9585010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@125222 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio/win')
-rw-r--r-- | media/audio/win/audio_low_latency_input_win.cc | 150 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_input_win.h | 39 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_input_win_unittest.cc | 38 | ||||
-rw-r--r-- | media/audio/win/wavein_input_win.cc | 6 | ||||
-rw-r--r-- | media/audio/win/wavein_input_win.h | 1 |
5 files changed, 163 insertions, 71 deletions
diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc index 708d889..839ace8 100644 --- a/media/audio/win/audio_low_latency_input_win.cc +++ b/media/audio/win/audio_low_latency_input_win.cc @@ -75,6 +75,7 @@ WASAPIAudioInputStream::WASAPIAudioInputStream( WASAPIAudioInputStream::~WASAPIAudioInputStream() {} bool WASAPIAudioInputStream::Open() { + DCHECK(CalledOnValidThread()); // Verify that we are not already opened. if (opened_) return false; @@ -83,23 +84,21 @@ bool WASAPIAudioInputStream::Open() { // device with the specified unique identifier or role which was // set at construction. HRESULT hr = SetCaptureDevice(); - if (FAILED(hr)) { + if (FAILED(hr)) return false; - } // Obtain an IAudioClient interface which enables us to create and initialize // an audio stream between an audio application and the audio engine. hr = ActivateCaptureDevice(); - if (FAILED(hr)) { + if (FAILED(hr)) return false; - } // Retrieve the stream format which the audio engine uses for its internal - // processing/mixing of shared-mode streams. + // processing/mixing of shared-mode streams. This function call is for + // diagnostic purposes only and only in debug mode. +#ifndef NDEBUG hr = GetAudioEngineStreamFormat(); - if (FAILED(hr)) { - return false; - } +#endif // Verify that the selected audio endpoint supports the specified format // set during construction. @@ -110,16 +109,13 @@ bool WASAPIAudioInputStream::Open() { // Initialize the audio stream between the client and the device using // shared mode and a lowest possible glitch-free latency. hr = InitializeAudioEngine(); - if (FAILED(hr)) { - return false; - } - - opened_ = true; - return true; + opened_ = SUCCEEDED(hr); + return opened_; } void WASAPIAudioInputStream::Start(AudioInputCallback* callback) { + DCHECK(CalledOnValidThread()); DCHECK(callback); DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully"; if (!opened_) @@ -144,6 +140,7 @@ void WASAPIAudioInputStream::Start(AudioInputCallback* callback) { } void WASAPIAudioInputStream::Stop() { + DCHECK(CalledOnValidThread()); if (!started_) return; @@ -183,23 +180,51 @@ void WASAPIAudioInputStream::Close() { } double WASAPIAudioInputStream::GetMaxVolume() { - // TODO(xians): Add volume support. - return 0.0; + // Verify that Open() has been called succesfully, to ensure that an audio + // session exists and that an ISimpleAudioVolume interface has been created. + DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully"; + if (!opened_) + return 0.0; + + // The effective volume value is always in the range 0.0 to 1.0, hence + // we can return a fixed value (=1.0) here. + return 1.0; } void WASAPIAudioInputStream::SetVolume(double volume) { - // TODO(xians): Add volume support. + DCHECK(CalledOnValidThread()); + DCHECK(volume <= 1.0 && volume >= 0.0); + + DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully"; + if (!opened_) + return; + + // Set a new master volume level. Valid volume levels are in the range + // 0.0 to 1.0. Ignore volume-change events. + HRESULT hr = simple_audio_volume_->SetMasterVolume(static_cast<float>(volume), + NULL); + DLOG_IF(WARNING, FAILED(hr)) << "Failed to set new input master volume."; } double WASAPIAudioInputStream::GetVolume() { - // TODO(xians): Add volume support. - return 0.0; + DCHECK(CalledOnValidThread()); + DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully"; + if (!opened_) + return 0.0; + + // Retrieve the current volume level. The value is in the range 0.0 to 1.0. + float level = 0.0f; + HRESULT hr = simple_audio_volume_->GetMasterVolume(&level); + DLOG_IF(WARNING, FAILED(hr)) << "Failed to get input master volume."; + + return static_cast<double>(level); } // static -double WASAPIAudioInputStream::HardwareSampleRate(ERole device_role) { +double WASAPIAudioInputStream::HardwareSampleRate( + const std::string& device_id) { base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; - HRESULT hr = GetMixFormat(device_role, &audio_engine_mix_format); + HRESULT hr = GetMixFormat(device_id, &audio_engine_mix_format); if (FAILED(hr)) return 0.0; @@ -207,9 +232,10 @@ double WASAPIAudioInputStream::HardwareSampleRate(ERole device_role) { } // static -uint32 WASAPIAudioInputStream::HardwareChannelCount(ERole device_role) { +uint32 WASAPIAudioInputStream::HardwareChannelCount( + const std::string& device_id) { base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; - HRESULT hr = GetMixFormat(device_role, &audio_engine_mix_format); + HRESULT hr = GetMixFormat(device_id, &audio_engine_mix_format); if (FAILED(hr)) return 0; @@ -217,7 +243,7 @@ uint32 WASAPIAudioInputStream::HardwareChannelCount(ERole device_role) { } // static -HRESULT WASAPIAudioInputStream::GetMixFormat(ERole device_role, +HRESULT WASAPIAudioInputStream::GetMixFormat(const std::string& device_id, WAVEFORMATEX** device_format) { // It is assumed that this static method is called from a COM thread, i.e., // CoInitializeEx() is not called here to avoid STA/MTA conflicts. @@ -231,9 +257,16 @@ HRESULT WASAPIAudioInputStream::GetMixFormat(ERole device_role, return hr; ScopedComPtr<IMMDevice> endpoint_device; - hr = enumerator->GetDefaultAudioEndpoint(eCapture, - device_role, - endpoint_device.Receive()); + if (device_id == AudioManagerBase::kDefaultDeviceId) { + // Retrieve the default capture audio endpoint. + hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, + endpoint_device.Receive()); + } else { + // Retrieve a capture endpoint device that is specified by an endpoint + // device-identification string. + hr = enumerator->GetDevice(UTF8ToUTF16(device_id).c_str(), + endpoint_device.Receive()); + } if (FAILED(hr)) { // This will happen if there's no audio capture device found or available // (e.g. some audio cards that have inputs will still report them as @@ -417,7 +450,7 @@ HRESULT WASAPIAudioInputStream::SetCaptureDevice() { if (SUCCEEDED(hr)) { // Retrieve the IMMDevice by using the specified role or the specified // unique endpoint device-identification string. - // TODO(henrika): possibly add suport for the eCommunications as well. + // TODO(henrika): possibly add support for the eCommunications as well. if (device_id_ == AudioManagerBase::kDefaultDeviceId) { // Retrieve the default capture audio endpoint for the specified role. // Note that, in Windows Vista, the MMDevice API supports device roles @@ -461,17 +494,55 @@ HRESULT WASAPIAudioInputStream::ActivateCaptureDevice() { } HRESULT WASAPIAudioInputStream::GetAudioEngineStreamFormat() { - // Retrieve the stream format that the audio engine uses for its internal - // processing/mixing of shared-mode streams. - return audio_client_->GetMixFormat(&audio_engine_mix_format_); + HRESULT hr = S_OK; +#ifndef NDEBUG + // The GetMixFormat() method retrieves the stream format that the + // audio engine uses for its internal processing of shared-mode streams. + // The method always uses a WAVEFORMATEXTENSIBLE structure, instead + // of a stand-alone WAVEFORMATEX structure, to specify the format. + // An WAVEFORMATEXTENSIBLE structure can specify both the mapping of + // channels to speakers and the number of bits of precision in each sample. + base::win::ScopedCoMem<WAVEFORMATEXTENSIBLE> format_ex; + hr = audio_client_->GetMixFormat( + reinterpret_cast<WAVEFORMATEX**>(&format_ex)); + + // See http://msdn.microsoft.com/en-us/windows/hardware/gg463006#EFH + // for details on the WAVE file format. + WAVEFORMATEX format = format_ex->Format; + DVLOG(2) << "WAVEFORMATEX:"; + DVLOG(2) << " wFormatTags : 0x" << std::hex << format.wFormatTag; + DVLOG(2) << " nChannels : " << format.nChannels; + DVLOG(2) << " nSamplesPerSec : " << format.nSamplesPerSec; + DVLOG(2) << " nAvgBytesPerSec: " << format.nAvgBytesPerSec; + DVLOG(2) << " nBlockAlign : " << format.nBlockAlign; + DVLOG(2) << " wBitsPerSample : " << format.wBitsPerSample; + DVLOG(2) << " cbSize : " << format.cbSize; + + DVLOG(2) << "WAVEFORMATEXTENSIBLE:"; + DVLOG(2) << " wValidBitsPerSample: " << + format_ex->Samples.wValidBitsPerSample; + DVLOG(2) << " dwChannelMask : 0x" << std::hex << + format_ex->dwChannelMask; + if (format_ex->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) + DVLOG(2) << " SubFormat : KSDATAFORMAT_SUBTYPE_PCM"; + else if (format_ex->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) + DVLOG(2) << " SubFormat : KSDATAFORMAT_SUBTYPE_IEEE_FLOAT"; + else if (format_ex->SubFormat == KSDATAFORMAT_SUBTYPE_WAVEFORMATEX) + DVLOG(2) << " SubFormat : KSDATAFORMAT_SUBTYPE_WAVEFORMATEX"; +#endif + return hr; } bool WASAPIAudioInputStream::DesiredFormatIsSupported() { - // In shared mode, the audio engine always supports the mix format, - // which is stored in the |audio_engine_mix_format_| member. In addition, - // the audio engine *might* support similar formats that have the same - // sample rate and number of channels as the mix format but differ in - // the representation of audio sample values. + // An application that uses WASAPI to manage shared-mode streams can rely + // on the audio engine to perform only limited format conversions. The audio + // engine can convert between a standard PCM sample size used by the + // application and the floating-point samples that the engine uses for its + // internal processing. However, the format for an application stream + // typically must have the same number of channels and the same sample + // rate as the stream format used by the device. + // Many audio devices support both PCM and non-PCM stream formats. However, + // the audio engine can mix only PCM streams. base::win::ScopedCoMem<WAVEFORMATEX> closest_match; HRESULT hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &format_, @@ -545,5 +616,12 @@ HRESULT WASAPIAudioInputStream::InitializeAudioEngine() { // enables us to read input data from the capture endpoint buffer. hr = audio_client_->GetService(__uuidof(IAudioCaptureClient), audio_capture_client_.ReceiveVoid()); + if (FAILED(hr)) + return hr; + + // Obtain a reference to the ISimpleAudioVolume interface which enables + // us to control the master volume level of an audio session. + hr = audio_client_->GetService(__uuidof(ISimpleAudioVolume), + simple_audio_volume_.ReceiveVoid()); return hr; } diff --git a/media/audio/win/audio_low_latency_input_win.h b/media/audio/win/audio_low_latency_input_win.h index 8f11feb..d34c025 100644 --- a/media/audio/win/audio_low_latency_input_win.h +++ b/media/audio/win/audio_low_latency_input_win.h @@ -49,6 +49,13 @@ // audio buffer is event driven. // - The Multimedia Class Scheduler service (MMCSS) is utilized to boost // the priority of the capture thread. +// - Audio applications that use the MMDevice API and WASAPI typically use +// the ISimpleAudioVolume interface to manage stream volume levels on a +// per-session basis. It is also possible to use of the IAudioEndpointVolume +// interface to control the master volume level of an audio endpoint device. +// This implementation is using the ISimpleAudioVolume interface. +// MSDN states that "In rare cases, a specialized audio application might +// require the use of the IAudioEndpointVolume". // #ifndef MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_INPUT_WIN_H_ #define MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_INPUT_WIN_H_ @@ -56,7 +63,10 @@ #include <Audioclient.h> #include <MMDeviceAPI.h> +#include <string> + #include "base/compiler_specific.h" +#include "base/threading/non_thread_safe.h" #include "base/threading/platform_thread.h" #include "base/threading/simple_thread.h" #include "base/win/scoped_co_mem.h" @@ -72,7 +82,8 @@ class AudioManagerWin; // AudioInputStream implementation using Windows Core Audio APIs. class MEDIA_EXPORT WASAPIAudioInputStream : public AudioInputStream, - public base::DelegateSimpleThread::Delegate { + public base::DelegateSimpleThread::Delegate, + NON_EXPORTED_BASE(public base::NonThreadSafe) { public: // The ctor takes all the usual parameters, plus |manager| which is the // the audio manager who is creating this object. @@ -93,12 +104,12 @@ class MEDIA_EXPORT WASAPIAudioInputStream virtual double GetVolume() OVERRIDE; // Retrieves the sample rate used by the audio engine for its internal - // processing/mixing of shared-mode streams. - static double HardwareSampleRate(ERole device_role); + // processing/mixing of shared-mode streams given a specifed device. + static double HardwareSampleRate(const std::string& device_id); // Retrieves the number of audio channels used by the audio engine for its - // internal processing/mixing of shared-mode streams. - static uint32 HardwareChannelCount(ERole device_role); + // internal processing/mixing of shared-mode streams given a specifed device. + static uint32 HardwareChannelCount(const std::string& device_id); bool started() const { return started_; } @@ -118,7 +129,8 @@ class MEDIA_EXPORT WASAPIAudioInputStream // Retrieves the stream format that the audio engine uses for its internal // processing/mixing of shared-mode streams. - static HRESULT GetMixFormat(ERole device_role, WAVEFORMATEX** device_format); + static HRESULT GetMixFormat(const std::string& device_id, + WAVEFORMATEX** device_format); // Initializes the COM library for use by the calling thread and set the // thread's concurrency model to multi-threaded. @@ -134,11 +146,6 @@ class MEDIA_EXPORT WASAPIAudioInputStream // Contains the desired audio format which is set up at construction. WAVEFORMATEX format_; - // Copy of the audio format which we know the audio engine supports. - // It is recommended to ensure that the sample rate in |format_| is identical - // to the sample rate in |audio_engine_mix_format_|. - base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format_; - bool opened_; bool started_; @@ -172,9 +179,13 @@ class MEDIA_EXPORT WASAPIAudioInputStream // Pointer to the object that will receive the recorded audio samples. AudioInputCallback* sink_; + // Windows Multimedia Device (MMDevice) API interfaces. + // An IMMDevice interface which represents an audio endpoint device. base::win::ScopedComPtr<IMMDevice> endpoint_device_; + // Windows Audio Session API (WASAP) interfaces. + // An IAudioClient interface which enables a client to create and initialize // an audio stream between an audio application and the audio engine. base::win::ScopedComPtr<IAudioClient> audio_client_; @@ -183,6 +194,12 @@ class MEDIA_EXPORT WASAPIAudioInputStream // from a capture endpoint buffer. base::win::ScopedComPtr<IAudioCaptureClient> audio_capture_client_; + // The ISimpleAudioVolume interface enables a client to control the + // master volume level of an audio session. + // The volume-level is a value in the range 0.0 to 1.0. + // This interface does only work with shared-mode streams. + base::win::ScopedComPtr<ISimpleAudioVolume> simple_audio_volume_; + // The audio engine will signal this event each time a buffer has been // recorded. base::win::ScopedHandle audio_samples_ready_event_; diff --git a/media/audio/win/audio_low_latency_input_win_unittest.cc b/media/audio/win/audio_low_latency_input_win_unittest.cc index a77620a..c4f6f9d 100644 --- a/media/audio/win/audio_low_latency_input_win_unittest.cc +++ b/media/audio/win/audio_low_latency_input_win_unittest.cc @@ -116,7 +116,8 @@ class AudioInputStreamWrapper { bits_per_sample_(16) { // Use native/mixing sample rate and 10ms frame size as default. sample_rate_ = static_cast<int>( - WASAPIAudioInputStream::HardwareSampleRate(eConsole)); + WASAPIAudioInputStream::HardwareSampleRate( + AudioManagerBase::kDefaultDeviceId)); samples_per_packet_ = sample_rate_ / 100; } @@ -170,10 +171,7 @@ static AudioInputStream* CreateDefaultAudioInputStream( } // Verify that we can retrieve the current hardware/mixing sample rate -// for all supported device roles. The ERole enumeration defines constants -// that indicate the role that the system/user has assigned to an audio -// endpoint device. -// TODO(henrika): modify this test when we support full device enumeration. +// for all available input devices. TEST(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); if (!CanRunAudioTests(audio_manager.get())) @@ -181,21 +179,19 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) { ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); - // Default device intended for games, system notification sounds, - // and voice commands. - int fs = static_cast<int>( - WASAPIAudioInputStream::HardwareSampleRate(eConsole)); - EXPECT_GE(fs, 0); - - // Default communication device intended for e.g. VoIP communication. - fs = static_cast<int>( - WASAPIAudioInputStream::HardwareSampleRate(eCommunications)); - EXPECT_GE(fs, 0); - - // Multimedia device for music, movies and live music recording. - fs = static_cast<int>( - WASAPIAudioInputStream::HardwareSampleRate(eMultimedia)); - EXPECT_GE(fs, 0); + // Retrieve a list of all available input devices. + media::AudioDeviceNames device_names; + audio_manager->GetAudioInputDeviceNames(&device_names); + + // Scan all available input devices and repeat the same test for all of them. + for (media::AudioDeviceNames::const_iterator it = device_names.begin(); + it != device_names.end(); ++it) { + // Retrieve the hardware sample rate given a specified audio input device. + // TODO(tommi): ensure that we don't have to cast here. + int fs = static_cast<int>(WASAPIAudioInputStream::HardwareSampleRate( + it->unique_id)); + EXPECT_GE(fs, 0); + } } // Test Create(), Close() calling sequence. @@ -374,7 +370,7 @@ TEST(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { LOG(INFO) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]"; WriteToFileAudioSink file_sink(file_name); - LOG(INFO) << ">> Speak into the microphone while recording."; + LOG(INFO) << ">> Speak into the default microphone while recording."; ais->Start(&file_sink); base::PlatformThread::Sleep(TestTimeouts::action_timeout_ms()); ais->Stop(); diff --git a/media/audio/win/wavein_input_win.cc b/media/audio/win/wavein_input_win.cc index 61d4b9f..10f0df0 100644 --- a/media/audio/win/wavein_input_win.cc +++ b/media/audio/win/wavein_input_win.cc @@ -188,16 +188,16 @@ void PCMWaveInAudioInputStream::Close() { } double PCMWaveInAudioInputStream::GetMaxVolume() { - // TODO(xians): Add volume support. + // TODO(henrika): Add volume support using the Audio Mixer API. return 0.0; } void PCMWaveInAudioInputStream::SetVolume(double volume) { - // TODO(xians): Add volume support. + // TODO(henrika): Add volume support using the Audio Mixer API. } double PCMWaveInAudioInputStream::GetVolume() { - // TODO(xians): Add volume support. + // TODO(henrika): Add volume support using the Audio Mixer API. return 0.0; } diff --git a/media/audio/win/wavein_input_win.h b/media/audio/win/wavein_input_win.h index 2ea6e08..66465a8 100644 --- a/media/audio/win/wavein_input_win.h +++ b/media/audio/win/wavein_input_win.h @@ -32,6 +32,7 @@ class PCMWaveInAudioInputStream : public AudioInputStream { virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + // TODO(henrika): Add volume support using the Audio Mixer API. virtual double GetMaxVolume() OVERRIDE; virtual void SetVolume(double volume) OVERRIDE; virtual double GetVolume() OVERRIDE; |