diff options
author | xians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-28 11:23:16 +0000 |
---|---|---|
committer | xians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-28 11:23:16 +0000 |
commit | 15b32be76a20a20c9e87bd444a58c4303d4cb569 (patch) | |
tree | 983f583012a719e0738726f950678de14825a239 /media | |
parent | dce9b486b4b01b0cc01030a18448f17a78f61052 (diff) | |
download | chromium_src-15b32be76a20a20c9e87bd444a58c4303d4cb569.zip chromium_src-15b32be76a20a20c9e87bd444a58c4303d4cb569.tar.gz chromium_src-15b32be76a20a20c9e87bd444a58c4303d4cb569.tar.bz2 |
Adding microphone volume support to chrome in linux. Totally there are 3 APIs are added: Get/SetMicVolume() and GetMaxMicVolume(). And a new AudioInputVolumeTest is also added to media_unittests.
BUG=115087
TEST=media_unittests
Review URL: http://codereview.chromium.org/9418042
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@123959 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_input_volume_unittest.cc | 151 | ||||
-rw-r--r-- | media/audio/audio_io.h | 12 | ||||
-rw-r--r-- | media/audio/fake_audio_input_stream.cc | 10 | ||||
-rw-r--r-- | media/audio/fake_audio_input_stream.h | 3 | ||||
-rw-r--r-- | media/audio/linux/alsa_input.cc | 90 | ||||
-rw-r--r-- | media/audio/linux/alsa_input.h | 9 | ||||
-rw-r--r-- | media/audio/linux/alsa_util.cc | 108 | ||||
-rw-r--r-- | media/audio/linux/alsa_util.h | 12 | ||||
-rw-r--r-- | media/audio/linux/alsa_wrapper.cc | 68 | ||||
-rw-r--r-- | media/audio/linux/alsa_wrapper.h | 23 | ||||
-rw-r--r-- | media/audio/mac/audio_input_mac.cc | 14 | ||||
-rw-r--r-- | media/audio/mac/audio_input_mac.h | 5 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_input_mac.cc | 173 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_input_mac.h | 14 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_input_win.cc | 14 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_input_win.h | 5 | ||||
-rw-r--r-- | media/audio/win/wavein_input_win.cc | 16 | ||||
-rw-r--r-- | media/audio/win/wavein_input_win.h | 5 | ||||
-rw-r--r-- | media/media.gyp | 1 |
19 files changed, 709 insertions, 24 deletions
diff --git a/media/audio/audio_input_volume_unittest.cc b/media/audio/audio_input_volume_unittest.cc new file mode 100644 index 0000000..5a006f0 --- /dev/null +++ b/media/audio/audio_input_volume_unittest.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cmath> + +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_com_initializer.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_manager_base.h" +#include "media/audio/audio_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::ScopedCOMInitializer; +using media::AudioDeviceNames; + +class AudioInputVolumeTest : public ::testing::Test { + protected: + AudioInputVolumeTest() + : audio_manager_(AudioManager::Create()), + com_init_(ScopedCOMInitializer::kMTA) { + } + + bool CanRunAudioTests() { + if (!audio_manager_.get()) + return false; + + return audio_manager_->HasAudioInputDevices(); + } + + // Helper method which checks if the stream has volume support. + bool HasDeviceVolumeControl(AudioInputStream* stream) { + if (!stream) + return false; + + return (stream->GetMaxVolume() != 0.0); + } + + AudioInputStream* CreateAndOpenStream(const std::string& device_id) { + AudioParameters::Format format = AudioParameters::AUDIO_PCM_LOW_LATENCY; + // TODO(xians): Implement a generic HardwareChannelCount API to query + // the number of channel for all the devices. + ChannelLayout channel_layout = + (media::GetAudioInputHardwareChannelCount() == 1) ? + CHANNEL_LAYOUT_MONO : CHANNEL_LAYOUT_STEREO; + int bits_per_sample = 16; + int sample_rate = + static_cast<int>(media::GetAudioInputHardwareSampleRate()); + int samples_per_packet = 0; +#if defined(OS_MACOSX) + samples_per_packet = (sample_rate / 100); +#elif defined(OS_LINUX) || defined(OS_OPENBSD) + samples_per_packet = (sample_rate / 100); +#elif defined(OS_WIN) + if (media::IsWASAPISupported()) { + if (sample_rate == 44100) + samples_per_packet = 448; + else + samples_per_packet = (sample_rate / 100); + } else { + samples_per_packet = 3 * (sample_rate / 100); + } +#else +#error Unsupported platform +#endif + AudioInputStream* ais = audio_manager_->MakeAudioInputStream( + AudioParameters(format, channel_layout, sample_rate, bits_per_sample, + samples_per_packet), + device_id); + EXPECT_TRUE(NULL != ais); + +#if defined(OS_LINUX) || defined(OS_OPENBSD) + // Some linux devices do not support our settings, we may fail to open + // those devices. + if (!ais->Open()) { + // Default device should always be able to be opened. + EXPECT_TRUE(AudioManagerBase::kDefaultDeviceId != device_id); + ais->Close(); + ais = NULL; + } +#elif defined(OS_WIN) || defined(OS_MACOSX) + EXPECT_TRUE(ais->Open()); +#endif + + return ais; + } + + scoped_ptr<AudioManager> audio_manager_; + ScopedCOMInitializer com_init_; +}; + +TEST_F(AudioInputVolumeTest, InputVolumeTest) { + if (!CanRunAudioTests()) + return; + + AudioDeviceNames device_names; + audio_manager_->GetAudioInputDeviceNames(&device_names); + DCHECK(!device_names.empty()); + + for (AudioDeviceNames::const_iterator it = device_names.begin(); + it != device_names.end(); + ++it) { + AudioInputStream* ais = CreateAndOpenStream(it->unique_id); + if (!ais) { + DLOG(WARNING) << "Failed to open stream for device " << it->unique_id; + continue; + } + + if ( !HasDeviceVolumeControl(ais)) { + DLOG(WARNING) << "Device: " << it->unique_id + << ", does not have volume control"; + ais->Close(); + continue; + } + + double max_volume = ais->GetMaxVolume(); + EXPECT_GT(max_volume, 0.0); + + // Notes that |original_volume| can be higher than |max_volume| on Linux. + double original_volume = ais->GetVolume(); + EXPECT_GE(original_volume, 0.0); +#if defined(OS_WIN) || defined(OS_MACOSX) + EXPECT_LE(original_volume, max_volume); +#endif + + // Tries to set the volume to |max_volume|. + ais->SetVolume(max_volume); + double current_volume = ais->GetVolume(); + EXPECT_EQ(max_volume, current_volume); + + // Tries to set the volume to zero. + double new_volume = 0.0; + ais->SetVolume(new_volume); + current_volume = ais->GetVolume(); + EXPECT_EQ(new_volume, current_volume); + + // Tries to set the volume to the middle. + new_volume = max_volume / 2; + ais->SetVolume(new_volume); + current_volume = ais->GetVolume(); + EXPECT_LT(current_volume, max_volume); + EXPECT_GT(current_volume, 0); + + // Restores the volume to the original value. + ais->SetVolume(original_volume); + current_volume = ais->GetVolume(); + EXPECT_EQ(original_volume, current_volume); + + ais->Close(); + } +} diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h index 173f4f5..f3b20ec 100644 --- a/media/audio/audio_io.h +++ b/media/audio/audio_io.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -154,6 +154,16 @@ class MEDIA_EXPORT AudioInputStream { // Close the stream. This also generates AudioInputCallback::OnClose(). This // should be the last call made on this object. virtual void Close() = 0; + + // Returns the maximum microphone analog volume or 0.0 if device does not + // have volume control. + virtual double GetMaxVolume() = 0; + + // Sets the microphone analog volume, with range [0, max_volume] inclusive. + virtual void SetVolume(double volume) = 0; + + // Returns the microphone analog volume, with range [0, max_volume] inclusive. + virtual double GetVolume() = 0; }; #endif // MEDIA_AUDIO_AUDIO_IO_H_ diff --git a/media/audio/fake_audio_input_stream.cc b/media/audio/fake_audio_input_stream.cc index f0f5c25..b9b271b 100644 --- a/media/audio/fake_audio_input_stream.cc +++ b/media/audio/fake_audio_input_stream.cc @@ -77,3 +77,13 @@ void FakeAudioInputStream::Close() { } Release(); // Destoys this object. } + +double FakeAudioInputStream::GetMaxVolume() { + return 0.0; +} + +void FakeAudioInputStream::SetVolume(double volume) {} + +double FakeAudioInputStream::GetVolume() { + return 0.0; +} diff --git a/media/audio/fake_audio_input_stream.h b/media/audio/fake_audio_input_stream.h index 7f1d38d..6bc4374 100644 --- a/media/audio/fake_audio_input_stream.h +++ b/media/audio/fake_audio_input_stream.h @@ -26,6 +26,9 @@ class FakeAudioInputStream virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; private: // Give RefCountedThreadSafe access our destructor. diff --git a/media/audio/linux/alsa_input.cc b/media/audio/linux/alsa_input.cc index 2e9a217..101c198 100644 --- a/media/audio/linux/alsa_input.cc +++ b/media/audio/linux/alsa_input.cc @@ -37,6 +37,8 @@ AlsaPcmInputStream::AlsaPcmInputStream(AudioManagerLinux* audio_manager, params.sample_rate), callback_(NULL), device_handle_(NULL), + mixer_handle_(NULL), + mixer_element_handle_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), read_callback_behind_schedule_(false) { } @@ -62,15 +64,16 @@ bool AlsaPcmInputStream::Open() { latency_us = std::max(latency_us, AlsaPcmOutputStream::kMinLatencyMicros); if (device_name_ == kAutoSelectDevice) { - device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, kDefaultDevice1, - params_.channels, - params_.sample_rate, - pcm_format, latency_us); - if (!device_handle_) { - device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, kDefaultDevice2, + const char* device_names[] = { kDefaultDevice1, kDefaultDevice2 }; + for (size_t i = 0; i < arraysize(device_names); ++i) { + device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, device_names[i], params_.channels, params_.sample_rate, pcm_format, latency_us); + if (device_handle_) { + device_name_ = device_names[i]; + break; + } } } else { device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, @@ -80,9 +83,17 @@ bool AlsaPcmInputStream::Open() { pcm_format, latency_us); } - if (device_handle_) + if (device_handle_) { audio_packet_.reset(new uint8[bytes_per_packet_]); + // Open the microphone mixer. + mixer_handle_ = alsa_util::OpenMixer(wrapper_, device_name_); + if (mixer_handle_) { + mixer_element_handle_ = alsa_util::LoadCaptureMixerElement( + wrapper_, mixer_handle_); + } + } + return device_handle_ != NULL; } @@ -239,7 +250,7 @@ void AlsaPcmInputStream::Close() { scoped_ptr<AlsaPcmInputStream> self_deleter(this); // Check in case we were already closed or not initialized yet. - if (!device_handle_ || !callback_) + if (!device_handle_) return; weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read. @@ -247,9 +258,70 @@ void AlsaPcmInputStream::Close() { if (error < 0) HandleError("PcmClose", error); + if (mixer_handle_) + alsa_util::CloseMixer(wrapper_, mixer_handle_, device_name_); + audio_packet_.reset(); device_handle_ = NULL; - callback_->OnClose(this); + + if (callback_) + callback_->OnClose(this); +} + +double AlsaPcmInputStream::GetMaxVolume() { + if (!mixer_handle_ || !mixer_element_handle_) { + DLOG(WARNING) << "GetMaxVolume is not supported for " << device_name_; + return 0.0; + } + + if (!wrapper_->MixerSelemHasCaptureVolume(mixer_element_handle_)) { + DLOG(WARNING) << "Unsupported microphone volume for " << device_name_; + return 0.0; + } + + long min = 0; + long max = 0; + if (wrapper_->MixerSelemGetCaptureVolumeRange(mixer_element_handle_, + &min, + &max)) { + DLOG(WARNING) << "Unsupported max microphone volume for " << device_name_; + return 0.0; + } + DCHECK(min == 0); + DCHECK(max > 0); + + return static_cast<double>(max); +} + +void AlsaPcmInputStream::SetVolume(double volume) { + if (!mixer_handle_ || !mixer_element_handle_) { + DLOG(WARNING) << "SetVolume is not supported for " << device_name_; + return; + } + + int error = wrapper_->MixerSelemSetCaptureVolumeAll( + mixer_element_handle_, static_cast<long>(volume)); + if (error < 0) { + DLOG(WARNING) << "Unable to set volume for " << device_name_; + } +} + +double AlsaPcmInputStream::GetVolume() { + if (!mixer_handle_ || !mixer_element_handle_) { + DLOG(WARNING) << "GetVolume is not supported for " << device_name_; + return 0.0; + } + + long current_volume = 0; + int error = wrapper_->MixerSelemGetCaptureVolume( + mixer_element_handle_, static_cast<snd_mixer_selem_channel_id_t>(0), + ¤t_volume); + if (error < 0) { + DLOG(WARNING) << "Unable to get volume for " << device_name_; + return 0.0; + } + + return static_cast<double>(current_volume); } void AlsaPcmInputStream::HandleError(const char* method, int error) { diff --git a/media/audio/linux/alsa_input.h b/media/audio/linux/alsa_input.h index c70f0de..4db2b9a 100644 --- a/media/audio/linux/alsa_input.h +++ b/media/audio/linux/alsa_input.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -36,11 +36,14 @@ class AlsaPcmInputStream : public AudioInputStream { AlsaWrapper* wrapper); virtual ~AlsaPcmInputStream(); - // Implementation of AudioOutputStream. + // Implementation of AudioInputStream. virtual bool Open() OVERRIDE; virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; private: // Logs the error and invokes any registered callbacks. @@ -70,6 +73,8 @@ class AlsaPcmInputStream : public AudioInputStream { AudioInputCallback* callback_; // Valid during a recording session. base::Time next_read_time_; // Scheduled time for the next read callback. snd_pcm_t* device_handle_; // Handle to the ALSA PCM recording device. + snd_mixer_t* mixer_handle_; // Handle to the ALSA microphone mixer. + snd_mixer_elem_t* mixer_element_handle_; // Handle to the capture element. base::WeakPtrFactory<AlsaPcmInputStream> weak_factory_; scoped_array<uint8> audio_packet_; // Buffer used for reading audio data. bool read_callback_behind_schedule_; diff --git a/media/audio/linux/alsa_util.cc b/media/audio/linux/alsa_util.cc index 55aa50e..5137119 100644 --- a/media/audio/linux/alsa_util.cc +++ b/media/audio/linux/alsa_util.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -33,7 +33,7 @@ static snd_pcm_t* OpenDevice(AlsaWrapper* wrapper, LOG(WARNING) << "PcmSetParams: " << device_name << ", " << wrapper->StrError(error) << " - Format: " << pcm_format << " Channels: " << channels << " Latency: " << latency_us; - if (!alsa_util::CloseDevice(wrapper, handle)) { + if (alsa_util::CloseDevice(wrapper, handle) < 0) { // TODO(ajwong): Retry on certain errors? LOG(WARNING) << "Unable to close audio device. Leaking handle."; } @@ -43,6 +43,25 @@ static snd_pcm_t* OpenDevice(AlsaWrapper* wrapper, return handle; } +static std::string DeviceNameToControlName(const std::string& device_name) { + const char kMixerPrefix[] = "hw"; + std::string control_name; + size_t pos1 = device_name.find(':'); + if (pos1 == std::string::npos) { + control_name = device_name; + } else { + // Examples: + // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel". + // deviceName: "default:CARD=Intel", controlName: "CARD=Intel". + size_t pos2 = device_name.find(','); + control_name = (pos2 == std::string::npos) ? + device_name.substr(pos1) : + kMixerPrefix + device_name.substr(pos1, pos2 - pos1); + } + + return control_name; +} + snd_pcm_format_t BitsToFormat(int bits_per_sample) { switch (bits_per_sample) { case 8: @@ -93,4 +112,89 @@ snd_pcm_t* OpenPlaybackDevice(AlsaWrapper* wrapper, sample_rate, pcm_format, latency_us); } +snd_mixer_t* OpenMixer(AlsaWrapper* wrapper, + const std::string& device_name) { + snd_mixer_t* mixer = NULL; + + int error = wrapper->MixerOpen(&mixer, 0); + if (error < 0) { + LOG(ERROR) << "MixerOpen: " << device_name << ", " + << wrapper->StrError(error); + return NULL; + } + + std::string control_name = DeviceNameToControlName(device_name); + error = wrapper->MixerAttach(mixer, control_name.c_str()); + if (error < 0) { + LOG(ERROR) << "MixerAttach, " << control_name << ", " + << wrapper->StrError(error); + alsa_util::CloseMixer(wrapper, mixer, device_name); + return NULL; + } + + error = wrapper->MixerElementRegister(mixer, NULL, NULL); + if (error < 0) { + LOG(ERROR) << "MixerElementRegister: " << control_name << ", " + << wrapper->StrError(error); + alsa_util::CloseMixer(wrapper, mixer, device_name); + return NULL; + } + + return mixer; +} + +void CloseMixer(AlsaWrapper* wrapper, snd_mixer_t* mixer, + const std::string& device_name) { + if (!mixer) + return; + + wrapper->MixerFree(mixer); + + int error = 0; + if (!device_name.empty()) { + std::string control_name = DeviceNameToControlName(device_name); + error = wrapper->MixerDetach(mixer, control_name.c_str()); + if (error < 0) { + LOG(WARNING) << "MixerDetach: " << control_name << ", " + << wrapper->StrError(error); + } + } + + error = wrapper->MixerClose(mixer); + if (error < 0) { + LOG(WARNING) << "MixerClose: " << wrapper->StrError(error); + } +} + +snd_mixer_elem_t* LoadCaptureMixerElement(AlsaWrapper* wrapper, + snd_mixer_t* mixer) { + if (!mixer) + return NULL; + + int error = wrapper->MixerLoad(mixer); + if (error < 0) { + LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error); + return NULL; + } + + snd_mixer_elem_t* elem = NULL; + snd_mixer_elem_t* mic_elem = NULL; + const char kCaptureElemName[] = "Capture"; + const char kMicElemName[] = "Mic"; + for (elem = wrapper->MixerFirstElem(mixer); + elem; + elem = wrapper->MixerNextElem(elem)) { + if (wrapper->MixerSelemIsActive(elem)) { + const char* elem_name = wrapper->MixerSelemName(elem); + if (strcmp(elem_name, kCaptureElemName) == 0) + return elem; + else if (strcmp(elem_name, kMicElemName) == 0) + mic_elem = elem; + } + } + + // Did not find any Capture handle, use the Mic handle. + return mic_elem; +} + } // namespace alsa_util diff --git a/media/audio/linux/alsa_util.h b/media/audio/linux/alsa_util.h index 5c9a0a0..054d24d 100644 --- a/media/audio/linux/alsa_util.h +++ b/media/audio/linux/alsa_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,6 +6,7 @@ #define MEDIA_AUDIO_LINUX_ALSA_UTIL_H_ #include <alsa/asoundlib.h> +#include <string> class AlsaWrapper; @@ -29,6 +30,15 @@ snd_pcm_t* OpenPlaybackDevice(AlsaWrapper* wrapper, int CloseDevice(AlsaWrapper* wrapper, snd_pcm_t* handle); +snd_mixer_t* OpenMixer(AlsaWrapper* wrapper, const std::string& device_name); + +void CloseMixer(AlsaWrapper* wrapper, + snd_mixer_t* mixer, + const std::string& device_name); + +snd_mixer_elem_t* LoadCaptureMixerElement(AlsaWrapper* wrapper, + snd_mixer_t* mixer); + } #endif // MEDIA_AUDIO_LINUX_ALSA_UTIL_H_ diff --git a/media/audio/linux/alsa_wrapper.cc b/media/audio/linux/alsa_wrapper.cc index 69b703e..9e056d8 100644 --- a/media/audio/linux/alsa_wrapper.cc +++ b/media/audio/linux/alsa_wrapper.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -128,7 +128,7 @@ int AlsaWrapper::PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format, unsigned int rate, int soft_resample, unsigned int latency) { int err = 0; - snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_t* hw_params; if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) return err; @@ -162,3 +162,67 @@ int AlsaWrapper::PcmStart(snd_pcm_t* handle) { return snd_pcm_start(handle); } +int AlsaWrapper::MixerOpen(snd_mixer_t** mixer, int mode) { + return snd_mixer_open(mixer, mode); +} + +int AlsaWrapper::MixerAttach(snd_mixer_t* mixer, const char* name) { + return snd_mixer_attach(mixer, name); +} + +int AlsaWrapper::MixerElementRegister(snd_mixer_t* mixer, + struct snd_mixer_selem_regopt* options, + snd_mixer_class_t** classp) { + return snd_mixer_selem_register(mixer, options, classp); +} + +void AlsaWrapper::MixerFree(snd_mixer_t* mixer) { + snd_mixer_free(mixer); +} + +int AlsaWrapper::MixerDetach(snd_mixer_t* mixer, const char* name) { + return snd_mixer_detach(mixer, name); +} + +int AlsaWrapper::MixerClose(snd_mixer_t* mixer) { + return snd_mixer_close(mixer); +} + +int AlsaWrapper::MixerLoad(snd_mixer_t* mixer) { + return snd_mixer_load(mixer); +} + +snd_mixer_elem_t* AlsaWrapper::MixerFirstElem(snd_mixer_t* mixer) { + return snd_mixer_first_elem(mixer); +} + +snd_mixer_elem_t* AlsaWrapper::MixerNextElem(snd_mixer_elem_t* elem) { + return snd_mixer_elem_next(elem); +} + +int AlsaWrapper::MixerSelemIsActive(snd_mixer_elem_t* elem) { + return snd_mixer_selem_is_active(elem); +} + +const char* AlsaWrapper::MixerSelemName(snd_mixer_elem_t* elem) { + return snd_mixer_selem_get_name(elem); +} + +int AlsaWrapper::MixerSelemSetCaptureVolumeAll( + snd_mixer_elem_t* elem, long value) { + return snd_mixer_selem_set_capture_volume_all(elem, value); +} + +int AlsaWrapper::MixerSelemGetCaptureVolume( + snd_mixer_elem_t* elem, snd_mixer_selem_channel_id_t channel, long* value) { + return snd_mixer_selem_get_capture_volume(elem, channel, value); +} + +int AlsaWrapper::MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem) { + return snd_mixer_selem_has_capture_volume(elem); +} + +int AlsaWrapper::MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem, + long* min, long* max) { + return snd_mixer_selem_get_capture_volume_range(elem, min, max); +} diff --git a/media/audio/linux/alsa_wrapper.h b/media/audio/linux/alsa_wrapper.h index 85cf68a..49d64b2 100644 --- a/media/audio/linux/alsa_wrapper.h +++ b/media/audio/linux/alsa_wrapper.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @@ -45,6 +45,27 @@ class MEDIA_EXPORT AlsaWrapper { virtual snd_pcm_state_t PcmState(snd_pcm_t* handle); virtual int PcmStart(snd_pcm_t* handle); + virtual int MixerOpen(snd_mixer_t** mixer, int mode); + virtual int MixerAttach(snd_mixer_t* mixer, const char* name); + virtual int MixerElementRegister(snd_mixer_t* mixer, + struct snd_mixer_selem_regopt* options, + snd_mixer_class_t** classp); + virtual void MixerFree(snd_mixer_t* mixer); + virtual int MixerDetach(snd_mixer_t* mixer, const char* name); + virtual int MixerClose(snd_mixer_t* mixer); + virtual int MixerLoad(snd_mixer_t* mixer); + virtual snd_mixer_elem_t* MixerFirstElem(snd_mixer_t* mixer); + virtual snd_mixer_elem_t* MixerNextElem(snd_mixer_elem_t* elem); + virtual int MixerSelemIsActive(snd_mixer_elem_t* elem); + virtual const char* MixerSelemName(snd_mixer_elem_t* elem); + virtual int MixerSelemSetCaptureVolumeAll(snd_mixer_elem_t* elem, long value); + virtual int MixerSelemGetCaptureVolume(snd_mixer_elem_t* elem, + snd_mixer_selem_channel_id_t channel, + long* value); + virtual int MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem); + virtual int MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem, + long* min, long* max); + virtual const char* StrError(int errnum); private: diff --git a/media/audio/mac/audio_input_mac.cc b/media/audio/mac/audio_input_mac.cc index 67e5ed5..e886ddf 100644 --- a/media/audio/mac/audio_input_mac.cc +++ b/media/audio/mac/audio_input_mac.cc @@ -113,6 +113,20 @@ void PCMQueueInAudioInputStream::Close() { // CARE: This object may now be destroyed. } +double PCMQueueInAudioInputStream::GetMaxVolume() { + NOTREACHED() << "Only supported for low-latency mode."; + return 0.0; +} + +void PCMQueueInAudioInputStream::SetVolume(double volume) { + NOTREACHED() << "Only supported for low-latency mode."; +} + +double PCMQueueInAudioInputStream::GetVolume() { + NOTREACHED() << "Only supported for low-latency mode."; + return 0.0; +} + void PCMQueueInAudioInputStream::HandleError(OSStatus err) { if (callback_) callback_->OnError(this, static_cast<int>(err)); diff --git a/media/audio/mac/audio_input_mac.h b/media/audio/mac/audio_input_mac.h index 26be177..6dad91d 100644 --- a/media/audio/mac/audio_input_mac.h +++ b/media/audio/mac/audio_input_mac.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -28,6 +28,9 @@ class PCMQueueInAudioInputStream : public AudioInputStream { virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; private: // Issue the OnError to |callback_|; diff --git a/media/audio/mac/audio_low_latency_input_mac.cc b/media/audio/mac/audio_low_latency_input_mac.cc index 36858ac..22b6df5 100644 --- a/media/audio/mac/audio_low_latency_input_mac.cc +++ b/media/audio/mac/audio_low_latency_input_mac.cc @@ -37,7 +37,8 @@ AUAudioInputStream::AUAudioInputStream( audio_unit_(0), input_device_id_(audio_device_id), started_(false), - hardware_latency_frames_(0) { + hardware_latency_frames_(0), + number_of_channels_in_frame_(0) { DCHECK(manager_); // Set up the desired (output) format specified by the client. @@ -84,8 +85,10 @@ bool AUAudioInputStream::Open() { return false; // Verify that we have a valid device. - if (input_device_id_ == kAudioObjectUnknown) + if (input_device_id_ == kAudioObjectUnknown) { + NOTREACHED() << "Device ID is unknown"; return false; + } // Start by obtaining an AudioOuputUnit using an AUHAL component description. @@ -211,6 +214,10 @@ bool AUAudioInputStream::Open() { // The hardware latency is fixed and will not change during the call. hardware_latency_frames_ = GetHardwareLatency(); + // The master channel is 0, Left and right are channels 1 and 2. + // And the master channel is not counted in |number_of_channels_in_frame_|. + number_of_channels_in_frame_ = GetNumberOfChannelsFromStream(); + return true; } @@ -263,6 +270,132 @@ void AUAudioInputStream::Close() { manager_->ReleaseInputStream(this); } +double AUAudioInputStream::GetMaxVolume() { + // Verify that we have a valid device. + if (input_device_id_ == kAudioObjectUnknown) { + NOTREACHED() << "Device ID is unknown"; + return 0.0; + } + + // Query if any of the master, left or right channels has volume control. + for (int i = 0; i <= number_of_channels_in_frame_; ++i) { + // If the volume is settable, the valid volume range is [0.0, 1.0]. + if (IsVolumeSettableOnChannel(i)) + return 1.0; + } + + // Volume control is not available for the audio stream. + return 0.0; +} + +void AUAudioInputStream::SetVolume(double volume) { + DCHECK(volume <= 1.0 && volume >= 0.0); + + // Verify that we have a valid device. + if (input_device_id_ == kAudioObjectUnknown) { + NOTREACHED() << "Device ID is unknown"; + return; + } + + Float32 volume_float32 = static_cast<Float32>(volume); + AudioObjectPropertyAddress property_address = { + kAudioDevicePropertyVolumeScalar, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster + }; + + // Try to set the volume for master volume channel. + if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) { + OSStatus result = AudioObjectSetPropertyData(input_device_id_, + &property_address, + 0, + NULL, + sizeof(volume_float32), + &volume_float32); + if (result != noErr) { + DLOG(WARNING) << "Failed to set volume to " << volume_float32; + } + return; + } + + // There is no master volume control, try to set volume for each channel. + int successful_channels = 0; + for (int i = 1; i <= number_of_channels_in_frame_; ++i) { + property_address.mElement = static_cast<UInt32>(i); + if (IsVolumeSettableOnChannel(i)) { + OSStatus result = AudioObjectSetPropertyData(input_device_id_, + &property_address, + 0, + NULL, + sizeof(volume_float32), + &volume_float32); + if (result == noErr) + ++successful_channels; + } + } + + DLOG_IF(WARNING, successful_channels == 0) + << "Failed to set volume to " << volume_float32; +} + +double AUAudioInputStream::GetVolume() { + // Verify that we have a valid device. + if (input_device_id_ == kAudioObjectUnknown){ + NOTREACHED() << "Device ID is unknown"; + return 0.0; + } + + AudioObjectPropertyAddress property_address = { + kAudioDevicePropertyVolumeScalar, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster + }; + + if (AudioObjectHasProperty(input_device_id_, &property_address)) { + // The device supports master volume control, get the volume from the + // master channel. + Float32 volume_float32 = 0.0; + UInt32 size = sizeof(volume_float32); + OSStatus result = AudioObjectGetPropertyData(input_device_id_, + &property_address, + 0, + NULL, + &size, + &volume_float32); + if (result == noErr) + return static_cast<double>(volume_float32); + } else { + // There is no master volume control, try to get the average volume of + // all the channels. + Float32 volume_float32 = 0.0; + int successful_channels = 0; + for (int i = 1; i <= number_of_channels_in_frame_; ++i) { + property_address.mElement = static_cast<UInt32>(i); + if (AudioObjectHasProperty(input_device_id_, &property_address)) { + Float32 channel_volume = 0; + UInt32 size = sizeof(channel_volume); + OSStatus result = AudioObjectGetPropertyData(input_device_id_, + &property_address, + 0, + NULL, + &size, + &channel_volume); + if (result == noErr) { + volume_float32 += channel_volume; + ++successful_channels; + } + } + } + + // Get the average volume of the channels. + if (successful_channels != 0) + return static_cast<double>(volume_float32 / successful_channels); + } + + DLOG(WARNING) << "Failed to get volume"; + return 0.0; +} + // AUHAL AudioDeviceOutput unit callback OSStatus AUAudioInputStream::InputProc(void* user_data, AudioUnitRenderActionFlags* flags, @@ -407,9 +540,45 @@ double AUAudioInputStream::GetCaptureLatency( return (delay_frames + hardware_latency_frames_); } +int AUAudioInputStream::GetNumberOfChannelsFromStream() { + // Get the stream format, to be able to read the number of channels. + AudioObjectPropertyAddress property_address = { + kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster + }; + AudioStreamBasicDescription stream_format; + UInt32 size = sizeof(stream_format); + OSStatus result = AudioObjectGetPropertyData(input_device_id_, + &property_address, + 0, + NULL, + &size, + &stream_format); + if (result != noErr) { + DLOG(WARNING) << "Could not get stream format"; + return 0; + } + + return static_cast<int>(stream_format.mChannelsPerFrame); +} + void AUAudioInputStream::HandleError(OSStatus err) { NOTREACHED() << "error " << GetMacOSStatusErrorString(err) << " (" << err << ")"; if (sink_) sink_->OnError(this, static_cast<int>(err)); } + +bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel) { + Boolean is_settable = false; + AudioObjectPropertyAddress property_address = { + kAudioDevicePropertyVolumeScalar, + kAudioDevicePropertyScopeInput, + static_cast<UInt32>(channel) + }; + OSStatus result = AudioObjectIsPropertySettable(input_device_id_, + &property_address, + &is_settable); + return (result == noErr) ? is_settable : false; +} diff --git a/media/audio/mac/audio_low_latency_input_mac.h b/media/audio/mac/audio_low_latency_input_mac.h index 6b5c720..baea801 100644 --- a/media/audio/mac/audio_low_latency_input_mac.h +++ b/media/audio/mac/audio_low_latency_input_mac.h @@ -61,6 +61,9 @@ class AUAudioInputStream : public AudioInputStream { virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; // Returns the current hardware sample rate for the default input device. static double HardwareSampleRate(); @@ -89,9 +92,16 @@ class AUAudioInputStream : public AudioInputStream { // Gets the current capture delay value. double GetCaptureLatency(const AudioTimeStamp* input_time_stamp); + // Gets the number of channels for a stream of audio data. + int GetNumberOfChannelsFromStream(); + // Issues the OnError() callback to the |sink_|. void HandleError(OSStatus err); + // Helper function to check if the volume control is avialable on specific + // channel. + bool IsVolumeSettableOnChannel(int channel); + // Our creator, the audio manager needs to be notified when we close. AudioManagerMac* manager_; @@ -126,6 +136,10 @@ class AUAudioInputStream : public AudioInputStream { // Fixed capture hardware latency in frames. double hardware_latency_frames_; + // The number of channels in each frame of audio data, which is used + // when querying the volume of each channel. + int number_of_channels_in_frame_; + DISALLOW_COPY_AND_ASSIGN(AUAudioInputStream); }; diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc index 172d821..708d889 100644 --- a/media/audio/win/audio_low_latency_input_win.cc +++ b/media/audio/win/audio_low_latency_input_win.cc @@ -182,6 +182,20 @@ void WASAPIAudioInputStream::Close() { manager_->ReleaseInputStream(this); } +double WASAPIAudioInputStream::GetMaxVolume() { + // TODO(xians): Add volume support. + return 0.0; +} + +void WASAPIAudioInputStream::SetVolume(double volume) { + // TODO(xians): Add volume support. +} + +double WASAPIAudioInputStream::GetVolume() { + // TODO(xians): Add volume support. + return 0.0; +} + // static double WASAPIAudioInputStream::HardwareSampleRate(ERole device_role) { base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; diff --git a/media/audio/win/audio_low_latency_input_win.h b/media/audio/win/audio_low_latency_input_win.h index e236e44..8f11feb 100644 --- a/media/audio/win/audio_low_latency_input_win.h +++ b/media/audio/win/audio_low_latency_input_win.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @@ -88,6 +88,9 @@ class MEDIA_EXPORT WASAPIAudioInputStream virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; // Retrieves the sample rate used by the audio engine for its internal // processing/mixing of shared-mode streams. diff --git a/media/audio/win/wavein_input_win.cc b/media/audio/win/wavein_input_win.cc index d3f1162..61d4b9f 100644 --- a/media/audio/win/wavein_input_win.cc +++ b/media/audio/win/wavein_input_win.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -187,6 +187,20 @@ void PCMWaveInAudioInputStream::Close() { manager_->ReleaseInputStream(this); } +double PCMWaveInAudioInputStream::GetMaxVolume() { + // TODO(xians): Add volume support. + return 0.0; +} + +void PCMWaveInAudioInputStream::SetVolume(double volume) { + // TODO(xians): Add volume support. +} + +double PCMWaveInAudioInputStream::GetVolume() { + // TODO(xians): Add volume support. + return 0.0; +} + void PCMWaveInAudioInputStream::HandleError(MMRESULT error) { DLOG(WARNING) << "PCMWaveInAudio error " << error; callback_->OnError(this, error); diff --git a/media/audio/win/wavein_input_win.h b/media/audio/win/wavein_input_win.h index ab8b95a..2ea6e08 100644 --- a/media/audio/win/wavein_input_win.h +++ b/media/audio/win/wavein_input_win.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -32,6 +32,9 @@ class PCMWaveInAudioInputStream : public AudioInputStream { virtual void Start(AudioInputCallback* callback) OVERRIDE; virtual void Stop() OVERRIDE; virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; private: enum State { diff --git a/media/media.gyp b/media/media.gyp index 5b91aad..8151571 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -573,6 +573,7 @@ 'audio/audio_input_controller_unittest.cc', 'audio/audio_input_device_unittest.cc', 'audio/audio_input_unittest.cc', + 'audio/audio_input_volume_unittest.cc', 'audio/audio_low_latency_input_output_unittest.cc', 'audio/audio_output_controller_unittest.cc', 'audio/audio_output_proxy_unittest.cc', |