summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorxians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-28 11:23:16 +0000
committerxians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-28 11:23:16 +0000
commit15b32be76a20a20c9e87bd444a58c4303d4cb569 (patch)
tree983f583012a719e0738726f950678de14825a239 /media
parentdce9b486b4b01b0cc01030a18448f17a78f61052 (diff)
downloadchromium_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.cc151
-rw-r--r--media/audio/audio_io.h12
-rw-r--r--media/audio/fake_audio_input_stream.cc10
-rw-r--r--media/audio/fake_audio_input_stream.h3
-rw-r--r--media/audio/linux/alsa_input.cc90
-rw-r--r--media/audio/linux/alsa_input.h9
-rw-r--r--media/audio/linux/alsa_util.cc108
-rw-r--r--media/audio/linux/alsa_util.h12
-rw-r--r--media/audio/linux/alsa_wrapper.cc68
-rw-r--r--media/audio/linux/alsa_wrapper.h23
-rw-r--r--media/audio/mac/audio_input_mac.cc14
-rw-r--r--media/audio/mac/audio_input_mac.h5
-rw-r--r--media/audio/mac/audio_low_latency_input_mac.cc173
-rw-r--r--media/audio/mac/audio_low_latency_input_mac.h14
-rw-r--r--media/audio/win/audio_low_latency_input_win.cc14
-rw-r--r--media/audio/win/audio_low_latency_input_win.h5
-rw-r--r--media/audio/win/wavein_input_win.cc16
-rw-r--r--media/audio/win/wavein_input_win.h5
-rw-r--r--media/media.gyp1
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),
+ &current_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',