diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-28 12:40:02 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-28 12:40:02 +0000 |
commit | dd414bf1a20b69891729b94b3d76ac714d475a81 (patch) | |
tree | 1d706835f6628100e23b86b4c9c0677a4bd46b74 | |
parent | fb82b78d9beeee9dfb947ddf86b34ef038bdd013 (diff) | |
download | chromium_src-dd414bf1a20b69891729b94b3d76ac714d475a81.zip chromium_src-dd414bf1a20b69891729b94b3d76ac714d475a81.tar.gz chromium_src-dd414bf1a20b69891729b94b3d76ac714d475a81.tar.bz2 |
Adds Analog Gain Control (AGC) to the WebRTC client.
The AGC functionality is as follows. It aims at maintaining the same speech loudness level from the microphone.
This is done by both controlling the analog microphone gain and applying a digital gain.
The microphone gain on the sound card is slowly increased/decreased during speech only. By observing the microphone control slider you can see it move when you speak. If you scream, the slider moves downwards and then upwards again when you return to normal.
It is not uncommon that the slider hits the maximum. This means that the maximum analog gain is not large enough to give the desired loudness. Nevertheless, we can in general still attain the desired loudness.
If the microphone control slider is moved manually, the analog adaptation restarts and returns to roughly the same position as before the change if the circumstances are still the same.
When the input microphone signal causes saturation, the level is decreased dramatically and has to re-adapt towards the old level.
The adaptation is a slowly varying process and at the beginning of a call this is noticed by a slow increase in volume.
Smaller changes in microphone input level is leveled out by the built-in digital control. For larger differences we need to rely on the slow adaptation.
BUG=115265
TEST=content_unittests
Review URL: https://chromiumcodereview.appspot.com/9702019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@129400 0039d316-1c4b-4281-b951-d872f2087c98
36 files changed, 662 insertions, 136 deletions
diff --git a/content/browser/renderer_host/media/audio_input_renderer_host.cc b/content/browser/renderer_host/media/audio_input_renderer_host.cc index 85d269f..121a5ac 100644 --- a/content/browser/renderer_host/media/audio_input_renderer_host.cc +++ b/content/browser/renderer_host/media/audio_input_renderer_host.cc @@ -174,7 +174,6 @@ bool AudioInputRendererHost::OnMessageReceived(const IPC::Message& message, IPC_MESSAGE_HANDLER(AudioInputHostMsg_CreateStream, OnCreateStream) IPC_MESSAGE_HANDLER(AudioInputHostMsg_RecordStream, OnRecordStream) IPC_MESSAGE_HANDLER(AudioInputHostMsg_CloseStream, OnCloseStream) - IPC_MESSAGE_HANDLER(AudioInputHostMsg_GetVolume, OnGetVolume) IPC_MESSAGE_HANDLER(AudioInputHostMsg_SetVolume, OnSetVolume) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP_EX() @@ -200,7 +199,8 @@ void AudioInputRendererHost::OnStartDevice(int stream_id, int session_id) { void AudioInputRendererHost::OnCreateStream(int stream_id, const AudioParameters& params, - const std::string& device_id) { + const std::string& device_id, + bool automatic_gain_control) { VLOG(1) << "AudioInputRendererHost::OnCreateStream(stream_id=" << stream_id << ")"; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); @@ -214,9 +214,11 @@ void AudioInputRendererHost::OnCreateStream(int stream_id, // Create a new AudioEntry structure. scoped_ptr<AudioEntry> entry(new AudioEntry()); + uint32 mem_size = sizeof(AudioInputBufferParameters) + buffer_size; + // Create the shared memory and share it with the renderer process // using a new SyncWriter object. - if (!entry->shared_memory.CreateAndMapAnonymous(buffer_size)) { + if (!entry->shared_memory.CreateAndMapAnonymous(mem_size)) { // If creation of shared memory failed then send an error message. SendErrorMessage(stream_id); return; @@ -248,6 +250,9 @@ void AudioInputRendererHost::OnCreateStream(int stream_id, return; } + // Set the initial AGC state for the audio input stream. + entry->controller->SetAutomaticGainControl(automatic_gain_control); + // If we have created the controller successfully create a entry and add it // to the map. entry->stream_id = stream_id; @@ -290,21 +295,7 @@ void AudioInputRendererHost::OnSetVolume(int stream_id, double volume) { return; } - // TODO(henrika): TBI. - NOTIMPLEMENTED(); -} - -void AudioInputRendererHost::OnGetVolume(int stream_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - AudioEntry* entry = LookupById(stream_id); - if (!entry) { - SendErrorMessage(stream_id); - return; - } - - // TODO(henrika): TBI. - NOTIMPLEMENTED(); + entry->controller->SetVolume(volume); } void AudioInputRendererHost::SendErrorMessage(int stream_id) { diff --git a/content/browser/renderer_host/media/audio_input_renderer_host.h b/content/browser/renderer_host/media/audio_input_renderer_host.h index 6d2a8b8..5a9cc6b 100644 --- a/content/browser/renderer_host/media/audio_input_renderer_host.h +++ b/content/browser/renderer_host/media/audio_input_renderer_host.h @@ -140,7 +140,8 @@ class CONTENT_EXPORT AudioInputRendererHost // required properties. void OnCreateStream(int stream_id, const AudioParameters& params, - const std::string& device_id); + const std::string& device_id, + bool automatic_gain_control); // Record the audio input stream referenced by |stream_id|. void OnRecordStream(int stream_id); @@ -151,9 +152,6 @@ class CONTENT_EXPORT AudioInputRendererHost // Set the volume of the audio stream referenced by |stream_id|. void OnSetVolume(int stream_id, double volume); - // Get the volume of the audio stream referenced by |stream_id|. - void OnGetVolume(int stream_id); - // Complete the process of creating an audio input stream. This will set up // the shared memory or shared socket in low latency mode. void DoCompleteCreation(media::AudioInputController* controller); diff --git a/content/browser/renderer_host/media/audio_input_sync_writer.cc b/content/browser/renderer_host/media/audio_input_sync_writer.cc index 9524ad0..d720017 100644 --- a/content/browser/renderer_host/media/audio_input_sync_writer.cc +++ b/content/browser/renderer_host/media/audio_input_sync_writer.cc @@ -15,15 +15,20 @@ AudioInputSyncWriter::AudioInputSyncWriter(base::SharedMemory* shared_memory) AudioInputSyncWriter::~AudioInputSyncWriter() {} +// TODO(henrika): Combine into one method (including Write). void AudioInputSyncWriter::UpdateRecordedBytes(uint32 bytes) { socket_->Send(&bytes, sizeof(bytes)); } -uint32 AudioInputSyncWriter::Write(const void* data, uint32 size) { - uint32 write_size = std::min(size, shared_memory_->created_size()); - // Copy audio input samples from recorded data to shared memory. - memcpy(shared_memory_->memory(), data, write_size); - return write_size; +uint32 AudioInputSyncWriter::Write(const void* data, uint32 size, + double volume) { + AudioInputBuffer* buffer = + reinterpret_cast<AudioInputBuffer*>(shared_memory_->memory()); + buffer->params.volume = volume; + buffer->params.size = size; + memcpy(buffer->audio, data, size); + + return size; } void AudioInputSyncWriter::Close() { diff --git a/content/browser/renderer_host/media/audio_input_sync_writer.h b/content/browser/renderer_host/media/audio_input_sync_writer.h index 3114c35..77acd50 100644 --- a/content/browser/renderer_host/media/audio_input_sync_writer.h +++ b/content/browser/renderer_host/media/audio_input_sync_writer.h @@ -27,7 +27,7 @@ class AudioInputSyncWriter : public media::AudioInputController::SyncWriter { // media::AudioOutputController::SyncWriter implementation. virtual void UpdateRecordedBytes(uint32 bytes) OVERRIDE; - virtual uint32 Write(const void* data, uint32 size) OVERRIDE; + virtual uint32 Write(const void* data, uint32 size, double volume) OVERRIDE; virtual void Close() OVERRIDE; bool Init(); diff --git a/content/common/media/audio_messages.h b/content/common/media/audio_messages.h index bac2537..0acb35c 100644 --- a/content/common/media/audio_messages.h +++ b/content/common/media/audio_messages.h @@ -5,6 +5,8 @@ // IPC messages for the audio. // Multiply-included message file, hence no include guard. +#include <string> + #include "base/basictypes.h" #include "base/shared_memory.h" #include "base/sync_socket.h" @@ -91,10 +93,11 @@ IPC_MESSAGE_CONTROL2(AudioHostMsg_CreateStream, AudioParameters /* params */) // Request that got sent to browser for creating an audio input stream -IPC_MESSAGE_CONTROL3(AudioInputHostMsg_CreateStream, +IPC_MESSAGE_CONTROL4(AudioInputHostMsg_CreateStream, int /* stream_id */, AudioParameters /* params */, - std::string /* device_id */) + std::string /* device_id */, + bool /* automatic_gain_control */) // Start buffering and play the audio stream specified by stream_id. IPC_MESSAGE_CONTROL1(AudioHostMsg_PlayStream, @@ -120,11 +123,6 @@ IPC_MESSAGE_CONTROL1(AudioHostMsg_CloseStream, IPC_MESSAGE_CONTROL1(AudioInputHostMsg_CloseStream, int /* stream_id */) -// Get audio volume of the input stream specified by -// (render_view_id, stream_id). -IPC_MESSAGE_CONTROL1(AudioInputHostMsg_GetVolume, - int /* stream_id */) - // Set audio volume of the stream specified by stream_id. // TODO(hclam): change this to vector if we have channel numbers other than 2. IPC_MESSAGE_CONTROL2(AudioHostMsg_SetVolume, diff --git a/content/renderer/media/audio_input_device.cc b/content/renderer/media/audio_input_device.cc index 24e9da9..11a3938 100644 --- a/content/renderer/media/audio_input_device.cc +++ b/content/renderer/media/audio_input_device.cc @@ -47,7 +47,8 @@ AudioInputDevice::AudioInputDevice(const AudioParameters& params, volume_(1.0), stream_id_(0), session_id_(0), - pending_device_ready_(false) { + pending_device_ready_(false), + agc_is_enabled_(false) { filter_ = RenderThreadImpl::current()->audio_input_message_filter(); } @@ -82,15 +83,27 @@ void AudioInputDevice::Stop() { } bool AudioInputDevice::SetVolume(double volume) { - NOTIMPLEMENTED(); - return false; + if (volume < 0 || volume > 1.0) + return false; + + message_loop()->PostTask(FROM_HERE, + base::Bind(&AudioInputDevice::SetVolumeOnIOThread, this, volume)); + + return true; } bool AudioInputDevice::GetVolume(double* volume) { - NOTIMPLEMENTED(); + NOTREACHED(); return false; } +void AudioInputDevice::SetAutomaticGainControl(bool enabled) { + DVLOG(1) << "SetAutomaticGainControl(enabled=" << enabled << ")"; + message_loop()->PostTask(FROM_HERE, + base::Bind(&AudioInputDevice::SetAutomaticGainControlOnIOThread, + this, enabled)); +} + void AudioInputDevice::InitializeOnIOThread() { DCHECK(message_loop()->BelongsToCurrentThread()); // Make sure we don't call Start() more than once. @@ -104,7 +117,8 @@ void AudioInputDevice::InitializeOnIOThread() { // and create the stream when getting a OnDeviceReady() callback. if (!session_id_) { Send(new AudioInputHostMsg_CreateStream( - stream_id_, audio_parameters_, AudioManagerBase::kDefaultDeviceId)); + stream_id_, audio_parameters_, AudioManagerBase::kDefaultDeviceId, + agc_is_enabled_)); } else { Send(new AudioInputHostMsg_StartDevice(stream_id_, session_id_)); pending_device_ready_ = true; @@ -133,6 +147,7 @@ void AudioInputDevice::ShutDownOnIOThread() { stream_id_ = 0; session_id_ = 0; pending_device_ready_ = false; + agc_is_enabled_ = false; } // We can run into an issue where ShutDownOnIOThread is called right after @@ -153,6 +168,18 @@ void AudioInputDevice::SetVolumeOnIOThread(double volume) { Send(new AudioInputHostMsg_SetVolume(stream_id_, volume)); } +void AudioInputDevice::SetAutomaticGainControlOnIOThread(bool enabled) { + DCHECK(message_loop()->BelongsToCurrentThread()); + DCHECK_EQ(0, stream_id_) << + "The AGC state can not be modified while capturing is active."; + if (stream_id_) + return; + + // We simply store the new AGC setting here. This value will be used when + // a new stream is initialized and by GetAutomaticGainControl(). + agc_is_enabled_ = enabled; +} + void AudioInputDevice::OnStreamCreated( base::SharedMemoryHandle handle, base::SyncSocket::Handle socket_handle, @@ -250,7 +277,7 @@ void AudioInputDevice::OnDeviceReady(const std::string& device_id) { stream_id_ = 0; } else { Send(new AudioInputHostMsg_CreateStream(stream_id_, audio_parameters_, - device_id)); + device_id, agc_is_enabled_)); } pending_device_ready_ = false; @@ -286,8 +313,17 @@ void AudioInputDevice::AudioThreadCallback::MapSharedMemory() { } void AudioInputDevice::AudioThreadCallback::Process(int pending_data) { + // The shared memory represents parameters, size of the data buffer and the + // actual data buffer containing audio data. Map the memory into this + // structure and parse out parameters and the data area. + AudioInputBuffer* buffer = + reinterpret_cast<AudioInputBuffer*>(shared_memory_.memory()); + uint32 size = buffer->params.size; + DCHECK_EQ(size, memory_length_ - sizeof(AudioInputBufferParameters)); + double volume = buffer->params.volume; + int audio_delay_milliseconds = pending_data / bytes_per_ms_; - int16* memory = reinterpret_cast<int16*>(shared_memory_.memory()); + int16* memory = reinterpret_cast<int16*>(&buffer->audio[0]); const size_t number_of_frames = audio_parameters_.frames_per_buffer(); const int bytes_per_sample = sizeof(memory[0]); @@ -306,5 +342,5 @@ void AudioInputDevice::AudioThreadCallback::Process(int pending_data) { // Deliver captured data to the client in floating point format // and update the audio-delay measurement. capture_callback_->Capture(audio_data_, number_of_frames, - audio_delay_milliseconds); + audio_delay_milliseconds, volume); } diff --git a/content/renderer/media/audio_input_device.h b/content/renderer/media/audio_input_device.h index a91ee16..99cd03c 100644 --- a/content/renderer/media/audio_input_device.h +++ b/content/renderer/media/audio_input_device.h @@ -69,6 +69,7 @@ #define CONTENT_RENDERER_MEDIA_AUDIO_INPUT_DEVICE_H_ #pragma once +#include <string> #include <vector> #include "base/basictypes.h" @@ -95,7 +96,8 @@ class CONTENT_EXPORT AudioInputDevice public: virtual void Capture(const std::vector<float*>& audio_data, size_t number_of_frames, - size_t audio_delay_milliseconds) = 0; + size_t audio_delay_milliseconds, + double volume) = 0; virtual void OnCaptureError() = 0; protected: virtual ~CaptureCallback() {} @@ -149,8 +151,13 @@ class CONTENT_EXPORT AudioInputDevice return audio_parameters_.frames_per_buffer(); } + // Sets the Automatic Gain Control state to on or off. + // This method must be called before Start(). It will not have any effect + // if it is called while capturing has already started. + void SetAutomaticGainControl(bool enabled); + // Methods called on IO thread ---------------------------------------------- - // AudioInputMessageFilter::Delegate impl., called by AudioInputMessageFilter + // AudioInputMessageFilter::Delegate impl., called by AudioInputMessageFilter. virtual void OnStreamCreated(base::SharedMemoryHandle handle, base::SyncSocket::Handle socket_handle, uint32 length) OVERRIDE; @@ -168,6 +175,7 @@ class CONTENT_EXPORT AudioInputDevice void StartOnIOThread(); void ShutDownOnIOThread(); void SetVolumeOnIOThread(double volume); + void SetAutomaticGainControlOnIOThread(bool enabled); void Send(IPC::Message* message); @@ -198,6 +206,10 @@ class CONTENT_EXPORT AudioInputDevice // callback. Only modified on the IO thread. bool pending_device_ready_; + // Stores the Automatic Gain Control state. Default is false. + // Only modified on the IO thread. + bool agc_is_enabled_; + // Our audio thread callback class. See source file for details. class AudioThreadCallback; diff --git a/content/renderer/media/webrtc_audio_device_impl.cc b/content/renderer/media/webrtc_audio_device_impl.cc index fefb10e..a25bdca 100644 --- a/content/renderer/media/webrtc_audio_device_impl.cc +++ b/content/renderer/media/webrtc_audio_device_impl.cc @@ -12,6 +12,7 @@ #include "media/audio/audio_util.h" static const int64 kMillisecondsBetweenProcessCalls = 5000; +static const double kMaxVolumeLevel = 255.0; // Supported hardware sample rates for input and output sides. #if defined(OS_WIN) || defined(OS_MACOSX) @@ -40,7 +41,8 @@ WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl() bytes_per_sample_(0), initialized_(false), playing_(false), - recording_(false) { + recording_(false), + agc_is_enabled_(false) { DVLOG(1) << "WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl()"; DCHECK(RenderThreadImpl::current()) << "WebRtcAudioDeviceImpl must be constructed on the render thread"; @@ -132,11 +134,21 @@ void WebRtcAudioDeviceImpl::OnRenderError() { LOG(ERROR) << "OnRenderError()"; } -void WebRtcAudioDeviceImpl::Capture( - const std::vector<float*>& audio_data, - size_t number_of_frames, - size_t audio_delay_milliseconds) { +void WebRtcAudioDeviceImpl::Capture(const std::vector<float*>& audio_data, + size_t number_of_frames, + size_t audio_delay_milliseconds, + double volume) { DCHECK_LE(number_of_frames, input_buffer_size()); +#if defined(OS_WIN) || defined(OS_MACOSX) + DCHECK_LE(volume, 1.0); +#elif defined(OS_LINUX) || defined(OS_OPENBSD) + // We have a special situation on Linux where the microphone volume can be + // "higher than maximum". The input volume slider in the sound preference + // allows the user to set a scaling that is higher than 100%. It means that + // even if the reported maximum levels is N, the actual microphone level can + // go up to 1.5*N and that corresponds to a normalized |volume| of 1.5. + DCHECK_LE(volume, 1.5); +#endif int output_delay_ms = 0; { @@ -165,15 +177,17 @@ void WebRtcAudioDeviceImpl::Capture( const int bytes_per_10_msec = channels * samples_per_10_msec * bytes_per_sample_; size_t accumulated_audio_samples = 0; - char* audio_byte_buffer = reinterpret_cast<char*>(input_buffer_.get()); + // Map internal volume range of [0.0, 1.0] into [0, 255] used by the + // webrtc::VoiceEngine. + uint32_t current_mic_level = static_cast<uint32_t>(volume * kMaxVolumeLevel); + // Write audio samples in blocks of 10 milliseconds to the registered // webrtc::AudioTransport sink. Keep writing until our internal byte // buffer is empty. while (accumulated_audio_samples < number_of_frames) { - // Deliver 10ms of recorded PCM audio. - // TODO(henrika): add support for analog AGC? + // Deliver 10ms of recorded 16-bit linear PCM audio. audio_transport_callback_->RecordedDataIsAvailable( audio_byte_buffer, samples_per_10_msec, @@ -181,12 +195,24 @@ void WebRtcAudioDeviceImpl::Capture( channels, samples_per_sec, input_delay_ms_ + output_delay_ms, - 0, // clock_drift - 0, // current_mic_level - new_mic_level); // not used + 0, // TODO(henrika): |clock_drift| parameter is not utilized today. + current_mic_level, + new_mic_level); + accumulated_audio_samples += samples_per_10_msec; audio_byte_buffer += bytes_per_10_msec; } + + // The AGC returns a non-zero microphone level if it has been decided + // that a new level should be set. + if (new_mic_level != 0) { + // Use IPC and set the new level. Note that, it will take some time + // before the new level is effective due to the IPC scheme. + // During this time, |current_mic_level| will contain "non-valid" values + // and it might reduce the AGC performance. Measurements on Windows 7 have + // shown that we might receive old volume levels for one or two callbacks. + SetMicrophoneVolume(new_mic_level); + } } void WebRtcAudioDeviceImpl::OnCaptureError() { @@ -331,8 +357,8 @@ int32_t WebRtcAudioDeviceImpl::Init() { ChannelLayout out_channel_layout = CHANNEL_LAYOUT_MONO; AudioParameters::Format in_format = AudioParameters::AUDIO_PCM_LINEAR; - size_t in_buffer_size = 0; - size_t out_buffer_size = 0; + int in_buffer_size = 0; + int out_buffer_size = 0; // TODO(henrika): factor out all platform specific parts in separate // functions. Code is a bit messy right now. @@ -357,6 +383,8 @@ int32_t WebRtcAudioDeviceImpl::Init() { in_buffer_size = 440; } else { in_buffer_size = (in_sample_rate / 100); + DCHECK_EQ(in_buffer_size * 100, in_sample_rate) << + "Sample rate not supported. Should have been caught in Init()."; } // Render side: AUDIO_PCM_LOW_LATENCY is based on the Core Audio (WASAPI) @@ -401,6 +429,8 @@ int32_t WebRtcAudioDeviceImpl::Init() { in_buffer_size = 440; } else { in_buffer_size = (in_sample_rate / 100); + DCHECK_EQ(in_buffer_size * 100, in_sample_rate) << + "Sample rate not supported. Should have been caught in Init()."; } // Render side: AUDIO_PCM_LOW_LATENCY on Mac OS X is based on a callback- @@ -594,8 +624,8 @@ bool WebRtcAudioDeviceImpl::RecordingIsInitialized() const { int32_t WebRtcAudioDeviceImpl::StartPlayout() { DVLOG(1) << "StartPlayout()"; + LOG_IF(ERROR, !audio_transport_callback_) << "Audio transport is missing"; if (!audio_transport_callback_) { - LOG(ERROR) << "Audio transport is missing"; return -1; } if (playing_) { @@ -627,7 +657,6 @@ int32_t WebRtcAudioDeviceImpl::StartRecording() { DVLOG(1) << "StartRecording()"; LOG_IF(ERROR, !audio_transport_callback_) << "Audio transport is missing"; if (!audio_transport_callback_) { - LOG(ERROR) << "Audio transport is missing"; return -1; } @@ -675,13 +704,25 @@ bool WebRtcAudioDeviceImpl::Recording() const { } int32_t WebRtcAudioDeviceImpl::SetAGC(bool enable) { - DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetAGC() " << "NOT IMPLEMENTED"; - return -1; + DVLOG(1) << "SetAGC(enable=" << enable << ")"; + // The current implementation does not support changing the AGC state while + // recording. Using this approach simplifies the design and it is also + // inline with the latest WebRTC standard. + DCHECK(initialized_); + DCHECK(!recording_) << "Unable to set AGC state while recording is active."; + if (recording_) { + return -1; + } + + audio_input_device_->SetAutomaticGainControl(enable); + agc_is_enabled_ = enable; + return 0; } bool WebRtcAudioDeviceImpl::AGC() const { - DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::AGC() " << "NOT IMPLEMENTED"; - return false; + // To reduce the usage of IPC messages, an internal AGC state is used. + // TODO(henrika): investigate if there is a need for a "deeper" getter. + return agc_is_enabled_; } int32_t WebRtcAudioDeviceImpl::SetWaveOutVolume(uint16_t volume_left, @@ -754,8 +795,7 @@ int32_t WebRtcAudioDeviceImpl::MaxSpeakerVolume(uint32_t* max_volume) const { return -1; } -int32_t WebRtcAudioDeviceImpl::MinSpeakerVolume( - uint32_t* min_volume) const { +int32_t WebRtcAudioDeviceImpl::MinSpeakerVolume(uint32_t* min_volume) const { NOTIMPLEMENTED(); return -1; } @@ -772,32 +812,39 @@ int32_t WebRtcAudioDeviceImpl::MicrophoneVolumeIsAvailable(bool* available) { } int32_t WebRtcAudioDeviceImpl::SetMicrophoneVolume(uint32_t volume) { - NOTIMPLEMENTED(); - return -1; + DVLOG(1) << "SetMicrophoneVolume(" << volume << ")"; + if (volume > kMaxVolumeLevel) + return -1; + + // WebRTC uses a range of [0, 255] to represent the level of the microphone + // volume. The IPC channel between the renderer and browser process works + // with doubles in the [0.0, 1.0] range and we have to compensate for that. + double normalized_volume = static_cast<double>(volume / kMaxVolumeLevel); + audio_input_device_->SetVolume(normalized_volume); + return 0; } int32_t WebRtcAudioDeviceImpl::MicrophoneVolume(uint32_t* volume) const { - NOTIMPLEMENTED(); + // The microphone level is fed to this class using the Capture() callback + // and this external API should not be used. Additional IPC messages are + // required if support for this API is ever needed. + NOTREACHED(); return -1; } -int32_t WebRtcAudioDeviceImpl::MaxMicrophoneVolume( - uint32_t* max_volume) const { - DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::MaxMicrophoneVolume() " - << "NOT IMPLEMENTED"; - return -1; +int32_t WebRtcAudioDeviceImpl::MaxMicrophoneVolume(uint32_t* max_volume) const { + *max_volume = kMaxVolumeLevel; + return 0; } -int32_t WebRtcAudioDeviceImpl::MinMicrophoneVolume( - uint32_t* min_volume) const { - DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::MinMicrophoneVolume() " - << "NOT IMPLEMENTED"; - return -1; +int32_t WebRtcAudioDeviceImpl::MinMicrophoneVolume(uint32_t* min_volume) const { + *min_volume = 0; + return 0; } int32_t WebRtcAudioDeviceImpl::MicrophoneVolumeStepSize( uint16_t* step_size) const { - NOTIMPLEMENTED(); + NOTREACHED(); return -1; } diff --git a/content/renderer/media/webrtc_audio_device_impl.h b/content/renderer/media/webrtc_audio_device_impl.h index b3f3534..c00e129 100644 --- a/content/renderer/media/webrtc_audio_device_impl.h +++ b/content/renderer/media/webrtc_audio_device_impl.h @@ -49,7 +49,7 @@ // base->StartPlayout(ch); // base->StartSending(ch); // ... -// <== full-duplex audio session ==> +// <== full-duplex audio session with AGC enabled ==> // ... // base->DeleteChannel(ch); // base->Terminate(); @@ -57,15 +57,30 @@ // VoiceEngine::Delete(voe); // } // -// Note that, WebRtcAudioDeviceImpl::RegisterAudioCallback() will -// be called by the webrtc::VoiceEngine::Init() call and the -// webrtc::VoiceEngine is an webrtc::AudioTransport implementation. -// Hence, when the underlying audio layer wants data samples to be played out, -// the AudioDevice::RenderCallback() will be called, which in turn uses the -// registered webrtc::AudioTransport callback and feeds the data to the -// webrtc::VoiceEngine. +// webrtc::VoiceEngine::Init() calls these ADM methods (in this order): // -// The picture below illustrates the media flow on the capture side: +// RegisterAudioCallback(this) +// webrtc::VoiceEngine is an webrtc::AudioTransport implementation and +// implements the RecordedDataIsAvailable() and NeedMorePlayData() callbacks. +// +// Init() +// Creates and initializes the AudioDevice and AudioInputDevice objects. +// +// SetAGC(true) +// Enables the adaptive analog mode of the AGC which ensures that a +// suitable microphone volume level will be set. This scheme will affect +// the actual microphone control slider. +// +// Media example: +// +// When the underlying audio layer wants data samples to be played out, the +// AudioDevice::RenderCallback() will be called, which in turn uses the +// registered webrtc::AudioTransport callback and gets the data to be played +// out from the webrtc::VoiceEngine. +// +// The picture below illustrates the media flow on the capture side where the +// AudioInputDevice client acts as link between the renderer and browser +// process: // // .------------------. .----------------------. // (Native audio) => | AudioInputStream |-> OnData ->| AudioInputController |-. @@ -87,11 +102,106 @@ // The actual data is transferred via SharedMemory. IPC is not involved // in the actual media transfer. // +// AGC overview: +// +// It aims to maintain a constant speech loudness level from the microphone. +// This is done by both controlling the analog microphone gain and applying +// digital gain. The microphone gain on the sound card is slowly +// increased/decreased during speech only. By observing the microphone control +// slider you can see it move when you speak. If you scream, the slider moves +// downwards and then upwards again when you return to normal. It is not +// uncommon that the slider hits the maximum. This means that the maximum +// analog gain is not large enough to give the desired loudness. Nevertheless, +// we can in general still attain the desired loudness. If the microphone +// control slider is moved manually, the gain adaptation restarts and returns +// to roughly the same position as before the change if the circumstances are +// still the same. When the input microphone signal causes saturation, the +// level is decreased dramatically and has to re-adapt towards the old level. +// The adaptation is a slowly varying process and at the beginning of capture +// this is noticed by a slow increase in volume. Smaller changes in microphone +// input level is leveled out by the built-in digital control. For larger +// differences we need to rely on the slow adaptation. +// See http://en.wikipedia.org/wiki/Automatic_gain_control for more details. +// +// AGC implementation details: +// +// The adaptive analog mode of the AGC is always enabled for desktop platforms +// in WebRTC. +// +// Before recording starts, the ADM sets an AGC state in the +// AudioInputDevice by calling AudioInputDevice::SetAutomaticGainControl(true). +// +// A capture session with AGC is started up as follows (simplified): +// +// [renderer] +// | +// ADM::StartRecording() +// AudioInputDevice::InitializeOnIOThread() +// AudioInputHostMsg_CreateStream(..., agc=true) [IPC] +// | +// [IPC to the browser] +// | +// AudioInputRendererHost::OnCreateStream() +// AudioInputController::CreateLowLatency() +// AudioInputController::DoSetAutomaticGainControl(true) +// AudioInputStream::SetAutomaticGainControl(true) +// | +// AGC is now enabled in the media layer and streaming starts (details omitted). +// The figure below illustrates the AGC scheme which is active in combination +// with the default media flow explained earlier. +// | +// [browser] +// | +// AudioInputStream::(Capture thread loop) +// AudioInputStreamImpl::QueryAgcVolume() => new volume once per second +// AudioInputData::OnData(..., volume) +// AudioInputController::OnData(..., volume) +// AudioInputSyncWriter::Write(..., volume) +// | +// [volume | size | data] is sent to the renderer [shared memory] +// | +// [renderer] +// | +// AudioInputDevice::AudioThreadCallback::Process() +// WebRtcAudioDeviceImpl::Capture(..., volume) +// AudioTransport::RecordedDataIsAvailable(...,volume, new_volume) +// | +// The AGC now uses the current volume input and computes a suitable new +// level given by the |new_level| output. This value is only non-zero if the +// AGC has take a decision that the microphone level should change. +// | +// if (new_volume != 0) +// AudioInputDevice::SetVolume(new_volume) +// AudioInputHostMsg_SetVolume(new_volume) [IPC] +// | +// [IPC to the browser] +// | +// AudioInputRendererHost::OnSetVolume() +// AudioInputController::SetVolume() +// AudioInputStream::SetVolume(scaled_volume) +// | +// Here we set the new microphone level in the media layer and at the same time +// read the new setting (we might not get exactly what is set). +// | +// AudioInputData::OnData(..., updated_volume) +// AudioInputController::OnData(..., updated_volume) +// | +// | +// This process repeats until we stop capturing data. Note that, a common +// steady state is that the volume control reaches its max and the new_volume +// value from the AGC is zero. A loud voice input is required to break this +// state and start lowering the level again. +// // Implementation notes: // // - This class must be created on the main render thread. // - The webrtc::AudioDeviceModule is reference counted. -// - Recording is currently not supported on Mac OS X. +// - AGC is only supported in combination with the WASAPI-based audio layer +// on Windows, i.e., it is not supported on Windows XP. +// - All volume levels required for the AGC scheme are transfered in a +// normalized range [0.0, 1.0]. Scaling takes place in both endpoints +// (WebRTC client a media layer). This approach ensures that we can avoid +// transferring maximum levels between the renderer and the browser. // class CONTENT_EXPORT WebRtcAudioDeviceImpl : NON_EXPORTED_BASE(public webrtc::AudioDeviceModule), @@ -120,7 +230,8 @@ class CONTENT_EXPORT WebRtcAudioDeviceImpl // AudioInputDevice::CaptureCallback implementation. virtual void Capture(const std::vector<float*>& audio_data, size_t number_of_frames, - size_t audio_delay_milliseconds) OVERRIDE; + size_t audio_delay_milliseconds, + double volume) OVERRIDE; virtual void OnCaptureError() OVERRIDE; // AudioInputDevice::CaptureEventHandler implementation. @@ -336,6 +447,9 @@ class CONTENT_EXPORT WebRtcAudioDeviceImpl bool playing_; bool recording_; + // Local copy of the current Automatic Gain Control state. + bool agc_is_enabled_; + DISALLOW_COPY_AND_ASSIGN(WebRtcAudioDeviceImpl); }; diff --git a/content/renderer/media/webrtc_audio_device_unittest.cc b/content/renderer/media/webrtc_audio_device_unittest.cc index d9b291c..e9323f2 100644 --- a/content/renderer/media/webrtc_audio_device_unittest.cc +++ b/content/renderer/media/webrtc_audio_device_unittest.cc @@ -437,6 +437,7 @@ TEST_F(WebRTCAudioDeviceTest, PlayLocalFile) { EXPECT_EQ(0, base->StartPlayout(ch)); ScopedWebRTCPtr<webrtc::VoEFile> file(engine.get()); + ASSERT_TRUE(file.valid()); int duration = 0; EXPECT_EQ(0, file->GetFileDuration(file_path.c_str(), duration, webrtc::kFileFormatPcm16kHzFile)); @@ -465,8 +466,8 @@ TEST_F(WebRTCAudioDeviceTest, PlayLocalFile) { // where they are decoded and played out on the default audio output device. // Disabled when running headless since the bots don't have the required config. // TODO(henrika): improve quality by using a wideband codec, enabling noise- -// suppressions and perhaps also the digital AGC. -TEST_F(WebRTCAudioDeviceTest, FullDuplexAudio) { +// suppressions etc. +TEST_F(WebRTCAudioDeviceTest, FullDuplexAudioWithAGC) { if (IsRunningHeadless()) return; @@ -477,13 +478,13 @@ TEST_F(WebRTCAudioDeviceTest, FullDuplexAudio) { return; EXPECT_CALL(media_observer(), - OnSetAudioStreamStatus(_, 1, StrEq("created"))); + OnSetAudioStreamStatus(_, 1, StrEq("created"))); EXPECT_CALL(media_observer(), - OnSetAudioStreamPlaying(_, 1, true)); + OnSetAudioStreamPlaying(_, 1, true)); EXPECT_CALL(media_observer(), - OnSetAudioStreamStatus(_, 1, StrEq("closed"))); + OnSetAudioStreamStatus(_, 1, StrEq("closed"))); EXPECT_CALL(media_observer(), - OnDeleteAudioStream(_, 1)).Times(AnyNumber()); + OnDeleteAudioStream(_, 1)).Times(AnyNumber()); scoped_refptr<WebRtcAudioDeviceImpl> audio_device( new WebRtcAudioDeviceImpl()); @@ -496,10 +497,19 @@ TEST_F(WebRTCAudioDeviceTest, FullDuplexAudio) { int err = base->Init(audio_device); ASSERT_EQ(0, err); + ScopedWebRTCPtr<webrtc::VoEAudioProcessing> audio_processing(engine.get()); + ASSERT_TRUE(audio_processing.valid()); + bool enabled = false; + webrtc::AgcModes agc_mode = webrtc::kAgcDefault; + EXPECT_EQ(0, audio_processing->GetAgcStatus(enabled, agc_mode)); + EXPECT_TRUE(enabled); + EXPECT_EQ(agc_mode, webrtc::kAgcAdaptiveAnalog); + int ch = base->CreateChannel(); EXPECT_NE(-1, ch); ScopedWebRTCPtr<webrtc::VoENetwork> network(engine.get()); + ASSERT_TRUE(network.valid()); scoped_ptr<WebRTCTransportImpl> transport( new WebRTCTransportImpl(network.get())); EXPECT_EQ(0, network->RegisterExternalTransport(ch, *transport.get())); diff --git a/content/renderer/pepper/pepper_platform_audio_input_impl.cc b/content/renderer/pepper/pepper_platform_audio_input_impl.cc index f6c3244..6c42f75 100644 --- a/content/renderer/pepper/pepper_platform_audio_input_impl.cc +++ b/content/renderer/pepper/pepper_platform_audio_input_impl.cc @@ -127,7 +127,7 @@ void PepperPlatformAudioInputImpl::InitializeOnIOThread(int session_id) { if (!session_id) { // We will be notified by OnStreamCreated(). filter_->Send(new AudioInputHostMsg_CreateStream( - stream_id_, params_, AudioManagerBase::kDefaultDeviceId)); + stream_id_, params_, AudioManagerBase::kDefaultDeviceId, false)); } else { // We will be notified by OnDeviceReady(). filter_->Send(new AudioInputHostMsg_StartDevice(stream_id_, session_id)); @@ -229,7 +229,7 @@ void PepperPlatformAudioInputImpl::OnDeviceReady(const std::string& device_id) { } else { // We will be notified by OnStreamCreated(). filter_->Send(new AudioInputHostMsg_CreateStream(stream_id_, params_, - device_id)); + device_id, false)); } } diff --git a/media/audio/audio_input_controller.cc b/media/audio/audio_input_controller.cc index bf6f2ea..6120efb 100644 --- a/media/audio/audio_input_controller.cc +++ b/media/audio/audio_input_controller.cc @@ -24,7 +24,8 @@ AudioInputController::AudioInputController(EventHandler* handler, handler_(handler), stream_(NULL), state_(kEmpty), - sync_writer_(sync_writer) { + sync_writer_(sync_writer), + max_volume_(0.0) { DCHECK(creator_loop_); no_data_timer_.reset(new base::DelayTimer<AudioInputController>(FROM_HERE, base::TimeDelta::FromSeconds(kTimerResetInterval), @@ -112,6 +113,16 @@ void AudioInputController::Close(const base::Closure& closed_task) { FROM_HERE, base::Bind(&AudioInputController::DoClose, this), closed_task); } +void AudioInputController::SetVolume(double volume) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &AudioInputController::DoSetVolume, this, volume)); +} + +void AudioInputController::SetAutomaticGainControl(bool enabled) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &AudioInputController::DoSetAutomaticGainControl, this, enabled)); +} + void AudioInputController::DoCreate(AudioManager* audio_manager, const AudioParameters& params, const std::string& device_id) { @@ -174,6 +185,40 @@ void AudioInputController::DoReportError(int code) { handler_->OnError(this, code); } +void AudioInputController::DoSetVolume(double volume) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_GE(volume, 0); + DCHECK_LE(volume, 1.0); + + if (state_ != kCreated && state_ != kRecording) + return; + + // Only ask for the maximum volume at first call and use cached value + // for remaining function calls. + if (!max_volume_) { + max_volume_ = stream_->GetMaxVolume(); + } + + if (max_volume_ == 0.0) { + DLOG(WARNING) << "Failed to access input volume control"; + return; + } + + // Set the stream volume and scale to a range matched to the platform. + stream_->SetVolume(max_volume_ * volume); +} + +void AudioInputController::DoSetAutomaticGainControl(bool enabled) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_NE(state_, kRecording); + + // Ensure that the AGC state only can be modified before streaming starts. + if (state_ != kCreated || state_ == kRecording) + return; + + stream_->SetAutomaticGainControl(enabled); +} + void AudioInputController::DoReportNoDataError() { DCHECK(creator_loop_->BelongsToCurrentThread()); @@ -190,7 +235,8 @@ void AudioInputController::DoResetNoDataTimer() { } void AudioInputController::OnData(AudioInputStream* stream, const uint8* data, - uint32 size, uint32 hardware_delay_bytes) { + uint32 size, uint32 hardware_delay_bytes, + double volume) { { base::AutoLock auto_lock(lock_); if (state_ != kRecording) @@ -202,7 +248,7 @@ void AudioInputController::OnData(AudioInputStream* stream, const uint8* data, // Use SyncSocket if we are in a low-latency mode. if (LowLatencyMode()) { - sync_writer_->Write(data, size); + sync_writer_->Write(data, size, volume); sync_writer_->UpdateRecordedBytes(hardware_delay_bytes); return; } diff --git a/media/audio/audio_input_controller.h b/media/audio/audio_input_controller.h index feeb895..8fd69b0 100644 --- a/media/audio/audio_input_controller.h +++ b/media/audio/audio_input_controller.h @@ -97,7 +97,7 @@ class MEDIA_EXPORT AudioInputController // Write certain amount of data from |data|. This method returns // number of written bytes. - virtual uint32 Write(const void* data, uint32 size) = 0; + virtual uint32 Write(const void* data, uint32 size, double volume) = 0; // Close this synchronous writer. virtual void Close() = 0; @@ -157,10 +157,18 @@ class MEDIA_EXPORT AudioInputController // This method trampolines to the audio thread. virtual void Close(const base::Closure& closed_task); + // Sets the capture volume of the input stream. The value 0.0 corresponds + // to muted and 1.0 to maximum volume. + virtual void SetVolume(double volume); + + // Sets the Automatic Gain Control (AGC) state of the input stream. + // Changing the AGC state is not supported while recording is active. + virtual void SetAutomaticGainControl(bool enabled); + // AudioInputCallback implementation. Threading details depends on the // device-specific implementation. virtual void OnData(AudioInputStream* stream, const uint8* src, uint32 size, - uint32 hardware_delay_bytes) OVERRIDE; + uint32 hardware_delay_bytes, double volume) OVERRIDE; virtual void OnClose(AudioInputStream* stream) OVERRIDE; virtual void OnError(AudioInputStream* stream, int code) OVERRIDE; @@ -187,6 +195,8 @@ class MEDIA_EXPORT AudioInputController void DoRecord(); void DoClose(); void DoReportError(int code); + void DoSetVolume(double volume); + void DoSetAutomaticGainControl(bool enabled); // Methods which ensures that OnError() is triggered when data recording // times out. Both are called on the creating thread. @@ -229,6 +239,8 @@ class MEDIA_EXPORT AudioInputController static Factory* factory_; + double max_volume_; + DISALLOW_COPY_AND_ASSIGN(AudioInputController); }; diff --git a/media/audio/audio_input_controller_unittest.cc b/media/audio/audio_input_controller_unittest.cc index 869610e..be6f8f3 100644 --- a/media/audio/audio_input_controller_unittest.cc +++ b/media/audio/audio_input_controller_unittest.cc @@ -25,10 +25,13 @@ static const int kChannels = 2; static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; static const int kSamplesPerPacket = kSampleRate / 10; +// Posts MessageLoop::QuitClosure() on specified message loop. ACTION_P(QuitMessageLoop, loop_or_proxy) { loop_or_proxy->PostTask(FROM_HERE, MessageLoop::QuitClosure()); } +// Posts MessageLoop::QuitClosure() on specified message loop after a certain +// number of calls given by |limit|. ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop_or_proxy) { if (++*count >= limit) { loop_or_proxy->PostTask(FROM_HERE, MessageLoop::QuitClosure()); diff --git a/media/audio/audio_input_stream_impl.cc b/media/audio/audio_input_stream_impl.cc new file mode 100644 index 0000000..529a9c4 --- /dev/null +++ b/media/audio/audio_input_stream_impl.cc @@ -0,0 +1,68 @@ +// 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 "base/logging.h" +#include "media/audio/audio_input_stream_impl.h" + +static const int kMinIntervalBetweenVolumeUpdatesMs = 1000; + +AudioInputStreamImpl::AudioInputStreamImpl() + : agc_is_enabled_(false), + max_volume_(0.0), + normalized_volume_(0.0) { +} + +AudioInputStreamImpl::~AudioInputStreamImpl() {} + +void AudioInputStreamImpl::SetAutomaticGainControl(bool enabled) { + agc_is_enabled_ = enabled; +} + +bool AudioInputStreamImpl::GetAutomaticGainControl() { + return agc_is_enabled_; +} + +void AudioInputStreamImpl::UpdateAgcVolume() { + base::AutoLock lock(lock_); + + // We take new volume samples once every second when the AGC is enabled. + // To ensure that a new setting has an immediate effect, the new volume + // setting is cached here. It will ensure that the next OnData() callback + // will contain a new valid volume level. If this approach was not taken, + // we could report invalid volume levels to the client for a time period + // of up to one second. + if (agc_is_enabled_) { + GetNormalizedVolume(); + } +} + +void AudioInputStreamImpl::QueryAgcVolume(double* normalized_volume) { + base::AutoLock lock(lock_); + + // Only modify the |volume| output reference if AGC is enabled and if + // more than one second has passed since the volume was updated the last time. + if (agc_is_enabled_) { + base::Time now = base::Time::Now(); + if ((now - last_volume_update_time_).InMilliseconds() > + kMinIntervalBetweenVolumeUpdatesMs) { + GetNormalizedVolume(); + last_volume_update_time_ = now; + } + *normalized_volume = normalized_volume_; + } +} + +void AudioInputStreamImpl::GetNormalizedVolume() { + if (max_volume_ == 0.0) { + // Cach the maximum volume if this is the first time we ask for it. + max_volume_ = GetMaxVolume(); + } + + if (max_volume_ != 0.0) { + // Retrieve the current volume level by asking the audio hardware. + // Range is normalized to [0.0,1.0] or [0.0, 1.5] on Linux. + normalized_volume_ = GetVolume() / max_volume_; + } +} + diff --git a/media/audio/audio_input_stream_impl.h b/media/audio/audio_input_stream_impl.h new file mode 100644 index 0000000..d223985 --- /dev/null +++ b/media/audio/audio_input_stream_impl.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef MEDIA_AUDIO_AUDIO_INPUT_STREAM_IMPL_H_ +#define MEDIA_AUDIO_AUDIO_INPUT_STREAM_IMPL_H_ + +#include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "base/time.h" +#include "media/audio/audio_io.h" + +// AudioInputStreamImpl implements platform-independent parts of the +// AudioInputStream interface. Each platform dependent implementation +// should derive from this class. +// TODO(henrika): we can probably break out more parts from our current +// AudioInputStream implementation and move out to this class. +class MEDIA_EXPORT AudioInputStreamImpl : public AudioInputStream { + public: + AudioInputStreamImpl(); + virtual ~AudioInputStreamImpl(); + + // Sets the automatic gain control (AGC) to on or off. When AGC is enabled, + // the microphone volume is queried periodically and the volume level is + // provided in each AudioInputCallback::OnData() callback and fed to the + // render-side AGC. + virtual void SetAutomaticGainControl(bool enabled) OVERRIDE; + + // Gets the current automatic gain control state. + virtual bool GetAutomaticGainControl() OVERRIDE; + + protected: + // Stores a new volume level by asking the audio hardware. + // This method only has an effect if AGC is enabled. + void UpdateAgcVolume(); + + // Gets the latest stored volume level if AGC is enabled and if + // more than one second has passed since the volume was updated the last time. + void QueryAgcVolume(double* normalized_volume); + + private: + // Takes a volume sample and stores it in |normalized_volume_|. + void GetNormalizedVolume(); + + // True when automatic gain control is enabled, false otherwise. + // Guarded by |lock_|. + bool agc_is_enabled_; + + // Stores the maximum volume which is used for normalization to a volume + // range of [0.0, 1.0]. + double max_volume_; + + // Contains last result of internal call to GetVolume(). We save resources + // but not querying the capture volume for each callback. Guarded by |lock_|. + // The range is normalized to [0.0, 1.0]. + double normalized_volume_; + + // Protects |agc_is_enabled_| and |volume_| . + base::Lock lock_; + + // Keeps track of the last time the microphone volume level was queried. + base::Time last_volume_update_time_; + + DISALLOW_COPY_AND_ASSIGN(AudioInputStreamImpl); +}; + +#endif // MEDIA_AUDIO_AUDIO_INPUT_STREAM_IMPL_H_ diff --git a/media/audio/audio_input_unittest.cc b/media/audio/audio_input_unittest.cc index 74effda..8d7b2ac 100644 --- a/media/audio/audio_input_unittest.cc +++ b/media/audio/audio_input_unittest.cc @@ -25,7 +25,7 @@ class TestInputCallback : public AudioInputStream::AudioInputCallback { max_data_bytes_(max_data_bytes) { } virtual void OnData(AudioInputStream* stream, const uint8* data, - uint32 size, uint32 hardware_delay_bytes) { + uint32 size, uint32 hardware_delay_bytes, double volume) { ++callback_count_; // Read the first byte to make sure memory is good. if (size) { diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h index f3b20ec..489e0ab 100644 --- a/media/audio/audio_io.h +++ b/media/audio/audio_io.h @@ -122,7 +122,8 @@ class MEDIA_EXPORT AudioInputStream { // available. This is called from a special audio thread and the // implementation should return as soon as possible. virtual void OnData(AudioInputStream* stream, const uint8* src, - uint32 size, uint32 hardware_delay_bytes) = 0; + uint32 size, uint32 hardware_delay_bytes, + double volume) = 0; // The stream is done with this callback, the last call received by this // audio sink. @@ -164,6 +165,12 @@ class MEDIA_EXPORT AudioInputStream { // Returns the microphone analog volume, with range [0, max_volume] inclusive. virtual double GetVolume() = 0; + + // Sets the Automatic Gain Control (AGC) state. + virtual void SetAutomaticGainControl(bool enabled) = 0; + + // Returns the Automatic Gain Control (AGC) state. + virtual bool GetAutomaticGainControl() = 0; }; #endif // MEDIA_AUDIO_AUDIO_IO_H_ diff --git a/media/audio/audio_low_latency_input_output_unittest.cc b/media/audio/audio_low_latency_input_output_unittest.cc index 57b3d4d..3a305b6 100644 --- a/media/audio/audio_low_latency_input_output_unittest.cc +++ b/media/audio/audio_low_latency_input_output_unittest.cc @@ -187,7 +187,8 @@ class FullDuplexAudioSinkSource // AudioInputStream::AudioInputCallback. virtual void OnData(AudioInputStream* stream, const uint8* src, uint32 size, - uint32 hardware_delay_bytes) OVERRIDE { + uint32 hardware_delay_bytes, + double volume) OVERRIDE { base::AutoLock lock(lock_); // Update three components in the AudioDelayState for this recorded diff --git a/media/audio/audio_parameters.h b/media/audio/audio_parameters.h index 6b3ed5c..392d328 100644 --- a/media/audio/audio_parameters.h +++ b/media/audio/audio_parameters.h @@ -9,6 +9,19 @@ #include "media/base/channel_layout.h" #include "media/base/media_export.h" +struct MEDIA_EXPORT AudioInputBufferParameters { + double volume; + uint32 size; +}; + +// Use a struct-in-struct approach to ensure that we can calculate the required +// size as sizeof(AudioInputBufferParameters) + #(bytes in audio buffer) without +// using packing. +struct MEDIA_EXPORT AudioInputBuffer { + AudioInputBufferParameters params; + int8 audio[1]; +}; + class MEDIA_EXPORT AudioParameters { public: // Compare is useful when AudioParameters is used as a key in std::map. diff --git a/media/audio/fake_audio_input_stream.cc b/media/audio/fake_audio_input_stream.cc index 0e4bb61..d5ba994 100644 --- a/media/audio/fake_audio_input_stream.cc +++ b/media/audio/fake_audio_input_stream.cc @@ -48,7 +48,7 @@ void FakeAudioInputStream::Start(AudioInputCallback* callback) { void FakeAudioInputStream::DoCallback() { DCHECK(callback_); - callback_->OnData(this, buffer_.get(), buffer_size_, buffer_size_); + callback_->OnData(this, buffer_.get(), buffer_size_, buffer_size_, 0.0); Time now = Time::Now(); base::TimeDelta next_callback_time = @@ -87,3 +87,9 @@ void FakeAudioInputStream::SetVolume(double volume) {} double FakeAudioInputStream::GetVolume() { return 0.0; } + +void FakeAudioInputStream::SetAutomaticGainControl(bool enabled) {} + +bool FakeAudioInputStream::GetAutomaticGainControl() { + return false; +} diff --git a/media/audio/fake_audio_input_stream.h b/media/audio/fake_audio_input_stream.h index 99668e8b..409b799 100644 --- a/media/audio/fake_audio_input_stream.h +++ b/media/audio/fake_audio_input_stream.h @@ -30,6 +30,8 @@ class MEDIA_EXPORT FakeAudioInputStream virtual double GetMaxVolume() OVERRIDE; virtual void SetVolume(double volume) OVERRIDE; virtual double GetVolume() OVERRIDE; + virtual void SetAutomaticGainControl(bool enabled) OVERRIDE; + virtual bool GetAutomaticGainControl() OVERRIDE; private: FakeAudioInputStream(AudioManagerBase* manager, diff --git a/media/audio/linux/alsa_input.cc b/media/audio/linux/alsa_input.cc index 43fe8e4d..e28867f 100644 --- a/media/audio/linux/alsa_input.cc +++ b/media/audio/linux/alsa_input.cc @@ -199,12 +199,19 @@ void AlsaPcmInputStream::ReadAudio() { int num_buffers_read = num_buffers; uint32 hardware_delay_bytes = static_cast<uint32>(GetCurrentDelay() * params_.GetBytesPerFrame()); + double normalized_volume = 0.0; + + // Update the AGC volume level once every second. Note that, |volume| is + // also updated each time SetVolume() is called through IPC by the + // render-side AGC. + QueryAgcVolume(&normalized_volume); + while (num_buffers--) { int frames_read = wrapper_->PcmReadi(device_handle_, audio_buffer_.get(), params_.frames_per_buffer()); if (frames_read == params_.frames_per_buffer()) { callback_->OnData(this, audio_buffer_.get(), bytes_per_buffer_, - hardware_delay_bytes); + hardware_delay_bytes, normalized_volume); } else { LOG(WARNING) << "PcmReadi returning less than expected frames: " << frames_read << " vs. " << params_.frames_per_buffer() @@ -303,6 +310,13 @@ void AlsaPcmInputStream::SetVolume(double volume) { if (error < 0) { DLOG(WARNING) << "Unable to set volume for " << device_name_; } + + // Update the AGC volume level based on the last setting above. Note that, + // the volume-level resolution is not infinite and it is therefore not + // possible to assume that the volume provided as input parameter can be + // used directly. Instead, a new query to the audio hardware is required. + // This method does nothing if AGC is disabled. + UpdateAgcVolume(); } double AlsaPcmInputStream::GetVolume() { diff --git a/media/audio/linux/alsa_input.h b/media/audio/linux/alsa_input.h index bbe965d..6f6a981 100644 --- a/media/audio/linux/alsa_input.h +++ b/media/audio/linux/alsa_input.h @@ -13,6 +13,7 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/time.h" +#include "media/audio/audio_input_stream_impl.h" #include "media/audio/audio_io.h" #include "media/audio/audio_parameters.h" @@ -22,7 +23,7 @@ class AudioManagerLinux; // Provides an input stream for audio capture based on the ALSA PCM interface. // This object is not thread safe and all methods should be invoked in the // thread that created the object. -class AlsaPcmInputStream : public AudioInputStream { +class AlsaPcmInputStream : public AudioInputStreamImpl { public: // Pass this to the constructor if you want to attempt auto-selection // of the audio recording device. diff --git a/media/audio/mac/audio_input_mac.cc b/media/audio/mac/audio_input_mac.cc index 61ab281..0277a00 100644 --- a/media/audio/mac/audio_input_mac.cc +++ b/media/audio/mac/audio_input_mac.cc @@ -127,6 +127,15 @@ double PCMQueueInAudioInputStream::GetVolume() { return 0.0; } +void PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) { + NOTREACHED() << "Only supported for low-latency mode."; +} + +bool PCMQueueInAudioInputStream::GetAutomaticGainControl() { + NOTREACHED() << "Only supported for low-latency mode."; + return false; +} + void PCMQueueInAudioInputStream::HandleError(OSStatus err) { if (callback_) callback_->OnError(this, static_cast<int>(err)); @@ -189,7 +198,8 @@ void PCMQueueInAudioInputStream::HandleInputBuffer( callback_->OnData(this, reinterpret_cast<const uint8*>(audio_buffer->mAudioData), audio_buffer->mAudioDataByteSize, - audio_buffer->mAudioDataByteSize); + audio_buffer->mAudioDataByteSize, + 0.0); // Recycle the buffer. OSStatus err = QueueNextBuffer(audio_buffer); if (err != noErr) { diff --git a/media/audio/mac/audio_input_mac.h b/media/audio/mac/audio_input_mac.h index 6dad91d..1347871 100644 --- a/media/audio/mac/audio_input_mac.h +++ b/media/audio/mac/audio_input_mac.h @@ -31,6 +31,8 @@ class PCMQueueInAudioInputStream : public AudioInputStream { virtual double GetMaxVolume() OVERRIDE; virtual void SetVolume(double volume) OVERRIDE; virtual double GetVolume() OVERRIDE; + virtual void SetAutomaticGainControl(bool enabled) OVERRIDE; + virtual bool GetAutomaticGainControl() 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 ddd12ed..075b055 100644 --- a/media/audio/mac/audio_low_latency_input_mac.cc +++ b/media/audio/mac/audio_low_latency_input_mac.cc @@ -12,6 +12,8 @@ #include "media/audio/audio_util.h" #include "media/audio/mac/audio_manager_mac.h" +static const int kMinIntervalBetweenVolumeUpdatesMs = 1000; + static std::ostream& operator<<(std::ostream& os, const AudioStreamBasicDescription& format) { os << "sample rate : " << format.mSampleRate << std::endl @@ -289,7 +291,9 @@ double AUAudioInputStream::GetMaxVolume() { } void AUAudioInputStream::SetVolume(double volume) { - DCHECK(volume <= 1.0 && volume >= 0.0); + DVLOG(1) << "SetVolume(volume=" << volume << ")"; + DCHECK_GE(volume, 0.0); + DCHECK_LE(volume, 1.0); // Verify that we have a valid device. if (input_device_id_ == kAudioObjectUnknown) { @@ -336,6 +340,13 @@ void AUAudioInputStream::SetVolume(double volume) { DLOG_IF(WARNING, successful_channels == 0) << "Failed to set volume to " << volume_float32; + + // Update the AGC volume level based on the last setting above. Note that, + // the volume-level resolution is not infinite and it is therefore not + // possible to assume that the volume provided as input parameter can be + // used directly. Instead, a new query to the audio hardware is required. + // This method does nothing if AGC is disabled. + UpdateAgcVolume(); } double AUAudioInputStream::GetVolume() { @@ -433,6 +444,12 @@ OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames, // Update the capture latency. double capture_latency_frames = GetCaptureLatency(time_stamp); + // Update the AGC volume level once every second. Note that, |volume| is + // also updated each time SetVolume() is called through IPC by the + // render-side AGC. + double normalized_volume = 0.0; + QueryAgcVolume(&normalized_volume); + AudioBuffer& buffer = io_data->mBuffers[0]; uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData); uint32 capture_delay_bytes = static_cast<uint32> @@ -441,7 +458,12 @@ OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames, if (!audio_data) return kAudioUnitErr_InvalidElement; - sink_->OnData(this, audio_data, buffer.mDataByteSize, capture_delay_bytes); + // Deliver data packet, delay estimation and volume level to the user. + sink_->OnData(this, + audio_data, + buffer.mDataByteSize, + capture_delay_bytes, + normalized_volume); return noErr; } diff --git a/media/audio/mac/audio_low_latency_input_mac.h b/media/audio/mac/audio_low_latency_input_mac.h index 6d1b899..d1e507f 100644 --- a/media/audio/mac/audio_low_latency_input_mac.h +++ b/media/audio/mac/audio_low_latency_input_mac.h @@ -38,14 +38,17 @@ #include <AudioUnit/AudioUnit.h> +#include "base/atomicops.h" #include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" +#include "base/time.h" #include "media/audio/audio_io.h" +#include "media/audio/audio_input_stream_impl.h" #include "media/audio/audio_parameters.h" class AudioManagerMac; -class AUAudioInputStream : public AudioInputStream { +class AUAudioInputStream : public AudioInputStreamImpl { public: // The ctor takes all the usual parameters, plus |manager| which is the // the audio manager who is creating this object. diff --git a/media/audio/mac/audio_low_latency_input_mac_unittest.cc b/media/audio/mac/audio_low_latency_input_mac_unittest.cc index a4619c3..139a860 100644 --- a/media/audio/mac/audio_low_latency_input_mac_unittest.cc +++ b/media/audio/mac/audio_low_latency_input_mac_unittest.cc @@ -28,9 +28,9 @@ ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) { class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { public: - MOCK_METHOD4(OnData, void(AudioInputStream* stream, + MOCK_METHOD5(OnData, void(AudioInputStream* stream, const uint8* src, uint32 size, - uint32 hardware_delay_bytes)); + uint32 hardware_delay_bytes, double volume)); MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code)); }; @@ -73,7 +73,7 @@ class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { // AudioInputStream::AudioInputCallback implementation. virtual void OnData(AudioInputStream* stream, const uint8* src, uint32 size, - uint32 hardware_delay_bytes) { + uint32 hardware_delay_bytes, double volume) { // Store data data in a temporary buffer to avoid making blocking // fwrite() calls in the audio callback. The complete buffer will be // written to file in the destructor. @@ -232,7 +232,7 @@ TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) { // All should contain valid packets of the same size and a valid delay // estimate. EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, - Ge(bytes_per_packet))) + Ge(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); @@ -268,7 +268,7 @@ TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) { // All should contain valid packets of the same size and a valid delay // estimate. EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, - Ge(bytes_per_packet))) + Ge(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc index 8344aba..214b8bf9 100644 --- a/media/audio/win/audio_low_latency_input_win.cc +++ b/media/audio/win/audio_low_latency_input_win.cc @@ -192,8 +192,10 @@ double WASAPIAudioInputStream::GetMaxVolume() { } void WASAPIAudioInputStream::SetVolume(double volume) { + DVLOG(1) << "SetVolume(volume=" << volume << ")"; DCHECK(CalledOnValidThread()); - DCHECK(volume <= 1.0 && volume >= 0.0); + DCHECK_GE(volume, 0.0); + DCHECK_LE(volume, 1.0); DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully"; if (!opened_) @@ -202,12 +204,18 @@ void WASAPIAudioInputStream::SetVolume(double volume) { // 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); + NULL); DLOG_IF(WARNING, FAILED(hr)) << "Failed to set new input master volume."; + + // Update the AGC volume level based on the last setting above. Note that, + // the volume-level resolution is not infinite and it is therefore not + // possible to assume that the volume provided as input parameter can be + // used directly. Instead, a new query to the audio hardware is required. + // This method does nothing if AGC is disabled. + UpdateAgcVolume(); } double WASAPIAudioInputStream::GetVolume() { - DCHECK(CalledOnValidThread()); DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully"; if (!opened_) return 0.0; @@ -323,6 +331,7 @@ void WASAPIAudioInputStream::Run() { LARGE_INTEGER now_count; bool recording = true; bool error = false; + double volume = GetVolume(); HANDLE wait_array[2] = {stop_capture_event_, audio_samples_ready_event_}; while (recording && !error) { @@ -389,6 +398,11 @@ void WASAPIAudioInputStream::Run() { first_audio_frame_timestamp) / 10000.0) * ms_to_frame_count_ + buffer_frame_index - num_frames_to_read; + // Update the AGC volume level once every second. Note that, + // |volume| is also updated each time SetVolume() is called + // through IPC by the render-side AGC. + QueryAgcVolume(&volume); + // Deliver captured data to the registered consumer using a packet // size which was specified at construction. uint32 delay_frames = static_cast<uint32>(audio_delay_frames + 0.5); @@ -396,11 +410,13 @@ void WASAPIAudioInputStream::Run() { uint8* audio_data = reinterpret_cast<uint8*>(capture_buffer.get()); - // Deliver data packet and delay estimation to the user. + // Deliver data packet, delay estimation and volume level to + // the user. sink_->OnData(this, audio_data, packet_size_bytes_, - delay_frames * frame_size_); + delay_frames * frame_size_, + volume); // Store parts of the recorded data which can't be delivered // using the current packet size. The stored section will be used diff --git a/media/audio/win/audio_low_latency_input_win.h b/media/audio/win/audio_low_latency_input_win.h index 276a342..d546829 100644 --- a/media/audio/win/audio_low_latency_input_win.h +++ b/media/audio/win/audio_low_latency_input_win.h @@ -73,7 +73,7 @@ #include "base/win/scoped_com_initializer.h" #include "base/win/scoped_comptr.h" #include "base/win/scoped_handle.h" -#include "media/audio/audio_io.h" +#include "media/audio/audio_input_stream_impl.h" #include "media/audio/audio_parameters.h" #include "media/base/media_export.h" @@ -81,7 +81,7 @@ class AudioManagerWin; // AudioInputStream implementation using Windows Core Audio APIs. class MEDIA_EXPORT WASAPIAudioInputStream - : public AudioInputStream, + : public AudioInputStreamImpl, public base::DelegateSimpleThread::Delegate, NON_EXPORTED_BASE(public base::NonThreadSafe) { public: 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 8afa95c..da5acb6 100644 --- a/media/audio/win/audio_low_latency_input_win_unittest.cc +++ b/media/audio/win/audio_low_latency_input_win_unittest.cc @@ -21,6 +21,7 @@ #include "testing/gtest/include/gtest/gtest.h" using base::win::ScopedCOMInitializer; +using ::testing::_; using ::testing::AnyNumber; using ::testing::AtLeast; using ::testing::Gt; @@ -34,9 +35,9 @@ ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) { class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { public: - MOCK_METHOD4(OnData, void(AudioInputStream* stream, + MOCK_METHOD5(OnData, void(AudioInputStream* stream, const uint8* src, uint32 size, - uint32 hardware_delay_bytes)); + uint32 hardware_delay_bytes, double volume)); MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code)); }; @@ -83,7 +84,8 @@ class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { virtual void OnData(AudioInputStream* stream, const uint8* src, uint32 size, - uint32 hardware_delay_bytes) { + uint32 hardware_delay_bytes, + double volume) { // Store data data in a temporary buffer to avoid making blocking // fwrite() calls in the audio callback. The complete buffer will be // written to file in the destructor. @@ -306,7 +308,7 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { // All should contain valid packets of the same size and a valid delay // estimate. EXPECT_CALL(sink, OnData( - ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet))) + ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); @@ -329,7 +331,7 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { (aisw.bits_per_sample() / 8); EXPECT_CALL(sink, OnData( - ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet))) + ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); @@ -349,7 +351,7 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { (aisw.bits_per_sample() / 8); EXPECT_CALL(sink, OnData( - ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet))) + ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc index 28dbac8..120a397 100644 --- a/media/audio/win/audio_low_latency_output_win.cc +++ b/media/audio/win/audio_low_latency_output_win.cc @@ -241,6 +241,7 @@ void WASAPIAudioOutputStream::Close() { } void WASAPIAudioOutputStream::SetVolume(double volume) { + DVLOG(1) << "SetVolume(volume=" << volume << ")"; float volume_float = static_cast<float>(volume); if (volume_float < 0.0f || volume_float > 1.0f) { return; @@ -249,6 +250,7 @@ void WASAPIAudioOutputStream::SetVolume(double volume) { } void WASAPIAudioOutputStream::GetVolume(double* volume) { + DVLOG(1) << "GetVolume()"; *volume = static_cast<double>(volume_); } diff --git a/media/audio/win/wavein_input_win.cc b/media/audio/win/wavein_input_win.cc index f8a4e7d..f4055db 100644 --- a/media/audio/win/wavein_input_win.cc +++ b/media/audio/win/wavein_input_win.cc @@ -4,8 +4,6 @@ #include "media/audio/win/wavein_input_win.h" -#include <windows.h> -#include <mmsystem.h> #pragma comment(lib, "winmm.lib") #include "base/logging.h" @@ -15,7 +13,7 @@ #include "media/audio/win/device_enumeration_win.h" namespace { -const int kStopInputStreamCallbackTimeout = 3000; // Three seconds. +const int kStopInputStreamCallbackTimeout = 3000; // Three seconds. } using media::AudioDeviceNames; @@ -201,6 +199,17 @@ double PCMWaveInAudioInputStream::GetVolume() { return 0.0; } +void PCMWaveInAudioInputStream::SetAutomaticGainControl(bool enabled) { + // TODO(henrika): Add AGC support when volume control has been added. + NOTIMPLEMENTED(); +} + +bool PCMWaveInAudioInputStream::GetAutomaticGainControl() { + // TODO(henrika): Add AGC support when volume control has been added. + NOTIMPLEMENTED(); + return false; +} + void PCMWaveInAudioInputStream::HandleError(MMRESULT error) { DLOG(WARNING) << "PCMWaveInAudio error " << error; callback_->OnError(this, error); @@ -260,10 +269,13 @@ void PCMWaveInAudioInputStream::WaveCallback(HWAVEIN hwi, UINT msg, // to the callback and check if we need to stop playing. // It should be OK to assume the data in the buffer is what has been // recorded in the soundcard. + // TODO(henrika): the |volume| parameter is always set to zero since there + // is currently no support for controlling the microphone volume level. WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1); obj->callback_->OnData(obj, reinterpret_cast<const uint8*>(buffer->lpData), buffer->dwBytesRecorded, - buffer->dwBytesRecorded); + buffer->dwBytesRecorded, + 0.0); if (obj->state_ == kStateStopping) { // The main thread has called Stop() and is waiting to issue waveOutReset diff --git a/media/audio/win/wavein_input_win.h b/media/audio/win/wavein_input_win.h index a9b4b91..b4418d8 100644 --- a/media/audio/win/wavein_input_win.h +++ b/media/audio/win/wavein_input_win.h @@ -5,6 +5,8 @@ #ifndef MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_ #define MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_ +#include <string> + #include <windows.h> #include <mmsystem.h> @@ -36,6 +38,8 @@ class PCMWaveInAudioInputStream : public AudioInputStream { virtual double GetMaxVolume() OVERRIDE; virtual void SetVolume(double volume) OVERRIDE; virtual double GetVolume() OVERRIDE; + virtual void SetAutomaticGainControl(bool enabled) OVERRIDE; + virtual bool GetAutomaticGainControl() OVERRIDE; private: enum State { diff --git a/media/media.gyp b/media/media.gyp index e60f2f2..9cdf84f 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -35,6 +35,8 @@ 'audio/audio_io.h', 'audio/audio_input_controller.cc', 'audio/audio_input_controller.h', + 'audio/audio_input_stream_impl.cc', + 'audio/audio_input_stream_impl.h', 'audio/audio_device_name.cc', 'audio/audio_device_name.h', 'audio/audio_manager.cc', |