summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authorxians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-26 18:23:06 +0000
committerxians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-26 18:23:06 +0000
commit594c95c47d5ca517c859eca0243d60ca3a63bee1 (patch)
treef75e2851ab603c0e91795e0fa78325f0415a5e1f /content
parent70486bea65dad07e5a53350df7287d48adb38299 (diff)
downloadchromium_src-594c95c47d5ca517c859eca0243d60ca3a63bee1.zip
chromium_src-594c95c47d5ca517c859eca0243d60ca3a63bee1.tar.gz
chromium_src-594c95c47d5ca517c859eca0243d60ca3a63bee1.tar.bz2
Added an "enable-audio-processor" flag and WebRtcAudioProcessor class.
This CL is a break-down CL from https://codereview.chromium.org/37793005. As the first step to move WebRtc APM to Chrome, it adds a enable-audio-processor command line flag, the WebRtcAudioProcessor class and its unittest. WebRtcAudioProcessor is not hooked up the any of the code in Chrome yet, but it will be exercised and tested by its unittest. TBR=tommi@chromium.org BUG=264611 TEST=content_unittest Review URL: https://codereview.chromium.org/88513002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237360 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/renderer_host/render_process_host_impl.cc1
-rw-r--r--content/content_renderer.gypi5
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/public/common/content_switches.cc10
-rw-r--r--content/public/common/content_switches.h1
-rw-r--r--content/renderer/media/media_stream_audio_processor.cc361
-rw-r--r--content/renderer/media/media_stream_audio_processor.h138
-rw-r--r--content/renderer/media/media_stream_audio_processor_options.cc96
-rw-r--r--content/renderer/media/media_stream_audio_processor_options.h53
-rw-r--r--content/renderer/media/media_stream_audio_processor_unittest.cc166
10 files changed, 829 insertions, 3 deletions
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index c744eae..245af8c 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1094,6 +1094,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
switches::kDisablePepper3d,
#endif
#if defined(ENABLE_WEBRTC)
+ switches::kEnableAudioTrackProcessing,
switches::kDisableDeviceEnumeration,
switches::kDisableSCTPDataChannels,
switches::kDisableWebRtcHWDecoding,
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index ac66122..5791a21 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -576,10 +576,15 @@
'../third_party/libjingle/libjingle.gyp:libjingle_webrtc',
'../third_party/libjingle/libjingle.gyp:libpeerconnection',
'../third_party/webrtc/modules/modules.gyp:audio_device',
+ '../third_party/webrtc/modules/modules.gyp:audio_processing',
'<(DEPTH)/crypto/crypto.gyp:crypto',
],
'sources': [
'public/renderer/webrtc_log_message_delegate.h',
+ 'renderer/media/media_stream_audio_processor.cc',
+ 'renderer/media/media_stream_audio_processor.h',
+ 'renderer/media/media_stream_audio_processor_options.cc',
+ 'renderer/media/media_stream_audio_processor_options.h',
'renderer/media/media_stream_center.cc',
'renderer/media/media_stream_dependency_factory.cc',
'renderer/media/media_stream_dispatcher.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index ec4fe06..f864519 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -681,6 +681,7 @@
'browser/renderer_host/p2p/socket_host_tcp_unittest.cc',
'browser/renderer_host/p2p/socket_host_tcp_server_unittest.cc',
'browser/renderer_host/p2p/socket_host_udp_unittest.cc',
+ 'renderer/media/media_stream_audio_processor_unittest.cc',
'renderer/media/media_stream_dependency_factory_unittest.cc',
'renderer/media/media_stream_dispatcher_unittest.cc',
'renderer/media/media_stream_impl_unittest.cc',
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index ddd769f..aca77b0 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -941,10 +941,14 @@ const char kZygoteCmdPrefix[] = "zygote-cmd-prefix";
const char kZygoteProcess[] = "zygote";
#if defined(ENABLE_WEBRTC)
-// Disable WebRTC device enumeration.
+// Enables audio processing in a MediaStreamTrack. When this flag is on, AEC,
+// NS and AGC will be done per MediaStreamTrack instead of in PeerConnection.
+const char kEnableAudioTrackProcessing[] = "enable-audio-track-processing";
+
+// Disables WebRTC device enumeration.
const char kDisableDeviceEnumeration[] = "disable-device-enumeration";
-// Disable WebRTC DataChannels SCTP wire protocol support.
+// Disables WebRTC DataChannels SCTP wire protocol support.
const char kDisableSCTPDataChannels[] = "disable-sctp-data-channels";
// Disables HW decode acceleration for WebRTC.
@@ -960,7 +964,7 @@ const char kDisableWebRtcHWEncoding[] = "disable-webrtc-hw-encoding";
// Enables WebRTC AEC recordings.
const char kEnableWebRtcAecRecordings[] = "enable-webrtc-aec-recordings";
-// Enable WebRTC to open TCP server sockets.
+// Enables WebRTC to open TCP server sockets.
const char kEnableWebRtcTcpServerSocket[] = "enable-webrtc-tcp-server-socket";
// Enables VP8 HW encode acceleration for WebRTC.
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index ea4a74a..b294909 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -268,6 +268,7 @@ CONTENT_EXPORT extern const char kZygoteCmdPrefix[];
CONTENT_EXPORT extern const char kZygoteProcess[];
#if defined(ENABLE_WEBRTC)
+CONTENT_EXPORT extern const char kEnableAudioTrackProcessing[];
CONTENT_EXPORT extern const char kDisableDeviceEnumeration[];
CONTENT_EXPORT extern const char kDisableSCTPDataChannels[];
CONTENT_EXPORT extern const char kDisableWebRtcHWDecoding[];
diff --git a/content/renderer/media/media_stream_audio_processor.cc b/content/renderer/media/media_stream_audio_processor.cc
new file mode 100644
index 0000000..07974aa
--- /dev/null
+++ b/content/renderer/media/media_stream_audio_processor.cc
@@ -0,0 +1,361 @@
+// Copyright 2013 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 "content/renderer/media/media_stream_audio_processor.h"
+
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "content/public/common/content_switches.h"
+#include "content/renderer/media/media_stream_audio_processor_options.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/audio_converter.h"
+#include "media/base/audio_fifo.h"
+#include "media/base/channel_layout.h"
+
+namespace content {
+
+namespace {
+
+using webrtc::AudioProcessing;
+using webrtc::MediaConstraintsInterface;
+
+#if defined(ANDROID)
+const int kAudioProcessingSampleRate = 16000;
+#else
+const int kAudioProcessingSampleRate = 32000;
+#endif
+const int kAudioProcessingNumberOfChannel = 1;
+
+const int kMaxNumberOfBuffersInFifo = 2;
+
+} // namespace
+
+class MediaStreamAudioProcessor::MediaStreamAudioConverter
+ : public media::AudioConverter::InputCallback {
+ public:
+ MediaStreamAudioConverter(const media::AudioParameters& source_params,
+ const media::AudioParameters& sink_params)
+ : source_params_(source_params),
+ sink_params_(sink_params),
+ audio_converter_(source_params, sink_params_, false) {
+ audio_converter_.AddInput(this);
+ // Create and initialize audio fifo and audio bus wrapper.
+ // The size of the FIFO should be at least twice of the source buffer size
+ // or twice of the sink buffer size.
+ int buffer_size = std::max(
+ kMaxNumberOfBuffersInFifo * source_params_.frames_per_buffer(),
+ kMaxNumberOfBuffersInFifo * sink_params_.frames_per_buffer());
+ fifo_.reset(new media::AudioFifo(source_params_.channels(), buffer_size));
+ // TODO(xians): Use CreateWrapper to save one memcpy.
+ audio_wrapper_ = media::AudioBus::Create(sink_params_.channels(),
+ sink_params_.frames_per_buffer());
+ }
+
+ virtual ~MediaStreamAudioConverter() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ audio_converter_.RemoveInput(this);
+ }
+
+ void Push(media::AudioBus* audio_source) {
+ // Called on the audio thread, which is the capture audio thread for
+ // |MediaStreamAudioProcessor::capture_converter_|, and render audio thread
+ // for |MediaStreamAudioProcessor::render_converter_|.
+ // And it must be the same thread as calling Convert().
+ DCHECK(thread_checker_.CalledOnValidThread());
+ fifo_->Push(audio_source);
+ }
+
+ bool Convert(webrtc::AudioFrame* out) {
+ // Called on the audio thread, which is the capture audio thread for
+ // |MediaStreamAudioProcessor::capture_converter_|, and render audio thread
+ // for |MediaStreamAudioProcessor::render_converter_|.
+ // Return false if there is no 10ms data in the FIFO.
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (fifo_->frames() < (source_params_.sample_rate() / 100))
+ return false;
+
+ // Convert 10ms data to the output format, this will trigger ProvideInput().
+ audio_converter_.Convert(audio_wrapper_.get());
+
+ // TODO(xians): Figure out a better way to handle the interleaved and
+ // deinterleaved format switching.
+ audio_wrapper_->ToInterleaved(audio_wrapper_->frames(),
+ sink_params_.bits_per_sample() / 8,
+ out->data_);
+
+ out->samples_per_channel_ = sink_params_.frames_per_buffer();
+ out->sample_rate_hz_ = sink_params_.sample_rate();
+ out->speech_type_ = webrtc::AudioFrame::kNormalSpeech;
+ out->vad_activity_ = webrtc::AudioFrame::kVadUnknown;
+ out->num_channels_ = sink_params_.channels();
+
+ return true;
+ }
+
+ const media::AudioParameters& source_parameters() const {
+ return source_params_;
+ }
+ const media::AudioParameters& sink_parameters() const {
+ return sink_params_;
+ }
+
+ private:
+ // AudioConverter::InputCallback implementation.
+ virtual double ProvideInput(media::AudioBus* audio_bus,
+ base::TimeDelta buffer_delay) OVERRIDE {
+ // Called on realtime audio thread.
+ // TODO(xians): Figure out why the first Convert() triggers ProvideInput
+ // two times.
+ if (fifo_->frames() < audio_bus->frames())
+ return 0;
+
+ fifo_->Consume(audio_bus, 0, audio_bus->frames());
+
+ // Return 1.0 to indicate no volume scaling on the data.
+ return 1.0;
+ }
+
+ base::ThreadChecker thread_checker_;
+ const media::AudioParameters source_params_;
+ const media::AudioParameters sink_params_;
+
+ // TODO(xians): consider using SincResampler to save some memcpy.
+ // Handles mixing and resampling between input and output parameters.
+ media::AudioConverter audio_converter_;
+ scoped_ptr<media::AudioBus> audio_wrapper_;
+ scoped_ptr<media::AudioFifo> fifo_;
+};
+
+MediaStreamAudioProcessor::MediaStreamAudioProcessor(
+ const webrtc::MediaConstraintsInterface* constraints)
+ : render_delay_ms_(0) {
+ capture_thread_checker_.DetachFromThread();
+ render_thread_checker_.DetachFromThread();
+ InitializeAudioProcessingModule(constraints);
+}
+
+MediaStreamAudioProcessor::~MediaStreamAudioProcessor() {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ StopAudioProcessing();
+}
+
+void MediaStreamAudioProcessor::PushCaptureData(media::AudioBus* audio_source) {
+ DCHECK(capture_thread_checker_.CalledOnValidThread());
+ capture_converter_->Push(audio_source);
+}
+
+void MediaStreamAudioProcessor::PushRenderData(
+ const int16* render_audio, int sample_rate, int number_of_channels,
+ int number_of_frames, base::TimeDelta render_delay) {
+ DCHECK(render_thread_checker_.CalledOnValidThread());
+
+ // Return immediately if the echo cancellation is off.
+ if (!audio_processing_ ||
+ !audio_processing_->echo_cancellation()->is_enabled()) {
+ return;
+ }
+
+ TRACE_EVENT0("audio",
+ "MediaStreamAudioProcessor::FeedRenderDataToAudioProcessing");
+ int64 new_render_delay_ms = render_delay.InMilliseconds();
+ DCHECK_LT(new_render_delay_ms,
+ std::numeric_limits<base::subtle::Atomic32>::max());
+ base::subtle::Release_Store(&render_delay_ms_, new_render_delay_ms);
+
+ InitializeRenderConverterIfNeeded(sample_rate, number_of_channels,
+ number_of_frames);
+
+ // TODO(xians): Avoid this extra interleave/deinterleave.
+ render_data_bus_->FromInterleaved(render_audio,
+ render_data_bus_->frames(),
+ sizeof(render_audio[0]));
+ render_converter_->Push(render_data_bus_.get());
+ while (render_converter_->Convert(&render_frame_))
+ audio_processing_->AnalyzeReverseStream(&render_frame_);
+}
+
+bool MediaStreamAudioProcessor::ProcessAndConsumeData(
+ base::TimeDelta capture_delay, int volume, bool key_pressed,
+ int16** out) {
+ DCHECK(capture_thread_checker_.CalledOnValidThread());
+ TRACE_EVENT0("audio",
+ "MediaStreamAudioProcessor::ProcessAndConsumeData");
+
+ if (!capture_converter_->Convert(&capture_frame_))
+ return false;
+
+ ProcessData(&capture_frame_, capture_delay, volume, key_pressed);
+ *out = capture_frame_.data_;
+
+ return true;
+}
+
+void MediaStreamAudioProcessor::SetCaptureFormat(
+ const media::AudioParameters& source_params) {
+ DCHECK(capture_thread_checker_.CalledOnValidThread());
+ DCHECK(source_params.IsValid());
+
+ // Create and initialize audio converter for the source data.
+ // When the webrtc AudioProcessing is enabled, the sink format of the
+ // converter will be the same as the post-processed data format, which is
+ // 32k mono for desktops and 16k mono for Android. When the AudioProcessing
+ // is disabled, the sink format will be the same as the source format.
+ const int sink_sample_rate = audio_processing_ ?
+ kAudioProcessingSampleRate : source_params.sample_rate();
+ const media::ChannelLayout sink_channel_layout = audio_processing_ ?
+ media::CHANNEL_LAYOUT_MONO : source_params.channel_layout();
+
+ // WebRtc is using 10ms data as its native packet size.
+ media::AudioParameters sink_params(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY, sink_channel_layout,
+ sink_sample_rate, 16, sink_sample_rate / 100);
+ capture_converter_.reset(
+ new MediaStreamAudioConverter(source_params, sink_params));
+}
+
+const media::AudioParameters& MediaStreamAudioProcessor::OutputFormat() const {
+ return capture_converter_->sink_parameters();
+}
+
+void MediaStreamAudioProcessor::InitializeAudioProcessingModule(
+ const webrtc::MediaConstraintsInterface* constraints) {
+ DCHECK(!audio_processing_);
+ DCHECK(constraints);
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAudioTrackProcessing)) {
+ return;
+ }
+
+ const bool enable_aec = GetPropertyFromConstraints(
+ constraints, MediaConstraintsInterface::kEchoCancellation);
+ const bool enable_ns = GetPropertyFromConstraints(
+ constraints, MediaConstraintsInterface::kNoiseSuppression);
+ const bool enable_high_pass_filter = GetPropertyFromConstraints(
+ constraints, MediaConstraintsInterface::kHighpassFilter);
+ const bool start_aec_dump = GetPropertyFromConstraints(
+ constraints, MediaConstraintsInterface::kInternalAecDump);
+#if defined(IOS) || defined(ANDROID)
+ const bool enable_experimental_aec = false;
+ const bool enable_typing_detection = false;
+#else
+ const bool enable_experimental_aec = GetPropertyFromConstraints(
+ constraints, MediaConstraintsInterface::kExperimentalEchoCancellation);
+ const bool enable_typing_detection = GetPropertyFromConstraints(
+ constraints, MediaConstraintsInterface::kTypingNoiseDetection);
+#endif
+
+ // Return immediately if no audio processing component is enabled.
+ if (!enable_aec && !enable_experimental_aec && !enable_ns &&
+ !enable_high_pass_filter && !enable_typing_detection) {
+ return;
+ }
+
+ // Create and configure the webrtc::AudioProcessing.
+ audio_processing_.reset(webrtc::AudioProcessing::Create(0));
+
+ // Enable the audio processing components.
+ if (enable_aec) {
+ EnableEchoCancellation(audio_processing_.get());
+ if (enable_experimental_aec)
+ EnableExperimentalEchoCancellation(audio_processing_.get());
+ }
+
+ if (enable_ns)
+ EnableNoiseSuppression(audio_processing_.get());
+
+ if (enable_high_pass_filter)
+ EnableHighPassFilter(audio_processing_.get());
+
+ if (enable_typing_detection)
+ EnableTypingDetection(audio_processing_.get());
+
+ if (enable_aec && start_aec_dump)
+ StartAecDump(audio_processing_.get());
+
+ // Configure the audio format the audio processing is running on. This
+ // has to be done after all the needed components are enabled.
+ CHECK_EQ(audio_processing_->set_sample_rate_hz(kAudioProcessingSampleRate),
+ 0);
+ CHECK_EQ(audio_processing_->set_num_channels(kAudioProcessingNumberOfChannel,
+ kAudioProcessingNumberOfChannel),
+ 0);
+}
+
+void MediaStreamAudioProcessor::InitializeRenderConverterIfNeeded(
+ int sample_rate, int number_of_channels, int frames_per_buffer) {
+ DCHECK(render_thread_checker_.CalledOnValidThread());
+ // TODO(xians): Figure out if we need to handle the buffer size change.
+ if (render_converter_.get() &&
+ render_converter_->source_parameters().sample_rate() == sample_rate &&
+ render_converter_->source_parameters().channels() == number_of_channels) {
+ // Do nothing if the |render_converter_| has been setup properly.
+ return;
+ }
+
+ // Create and initialize audio converter for the render data.
+ // webrtc::AudioProcessing accepts the same format as what it uses to process
+ // capture data, which is 32k mono for desktops and 16k mono for Android.
+ media::AudioParameters source_params(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::GuessChannelLayout(number_of_channels), sample_rate, 16,
+ frames_per_buffer);
+ media::AudioParameters sink_params(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_MONO, kAudioProcessingSampleRate, 16,
+ kAudioProcessingSampleRate / 100);
+ render_converter_.reset(
+ new MediaStreamAudioConverter(source_params, sink_params));
+ render_data_bus_ = media::AudioBus::Create(number_of_channels,
+ frames_per_buffer);
+}
+
+void MediaStreamAudioProcessor::ProcessData(webrtc::AudioFrame* audio_frame,
+ base::TimeDelta capture_delay,
+ int volume,
+ bool key_pressed) {
+ DCHECK(capture_thread_checker_.CalledOnValidThread());
+ if (!audio_processing_)
+ return;
+
+ TRACE_EVENT0("audio", "MediaStreamAudioProcessor::Process10MsData");
+ DCHECK_EQ(audio_processing_->sample_rate_hz(),
+ capture_converter_->sink_parameters().sample_rate());
+ DCHECK_EQ(audio_processing_->num_input_channels(),
+ capture_converter_->sink_parameters().channels());
+ DCHECK_EQ(audio_processing_->num_output_channels(),
+ capture_converter_->sink_parameters().channels());
+
+ base::subtle::Atomic32 render_delay_ms =
+ base::subtle::Acquire_Load(&render_delay_ms_);
+ int64 capture_delay_ms = capture_delay.InMilliseconds();
+ DCHECK_LT(capture_delay_ms,
+ std::numeric_limits<base::subtle::Atomic32>::max());
+ int total_delay_ms = capture_delay_ms + render_delay_ms;
+ if (total_delay_ms > 1000) {
+ LOG(WARNING) << "Large audio delay, capture delay: " << capture_delay_ms
+ << "ms; render delay: " << render_delay_ms << "ms";
+ }
+
+ audio_processing_->set_stream_delay_ms(total_delay_ms);
+ webrtc::GainControl* agc = audio_processing_->gain_control();
+ int err = agc->set_stream_analog_level(volume);
+ DCHECK_EQ(err, 0) << "set_stream_analog_level() error: " << err;
+ err = audio_processing_->ProcessStream(audio_frame);
+ DCHECK_EQ(err, 0) << "ProcessStream() error: " << err;
+
+ // TODO(xians): Add support for AGC, typing detection, audio level
+ // calculation, stereo swapping.
+}
+
+void MediaStreamAudioProcessor::StopAudioProcessing() {
+ if (!audio_processing_.get())
+ return;
+
+ // It is safe to stop the AEC dump even it is not started.
+ StopAecDump(audio_processing_.get());
+
+ audio_processing_.reset();
+}
+
+} // namespace content
diff --git a/content/renderer/media/media_stream_audio_processor.h b/content/renderer/media/media_stream_audio_processor.h
new file mode 100644
index 0000000..9c6db68
--- /dev/null
+++ b/content/renderer/media/media_stream_audio_processor.h
@@ -0,0 +1,138 @@
+// Copyright 2013 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 CONTENT_RENDERER_MEDIA_MEDIA_STREAM_AUDIO_PROCESSOR_H_
+#define CONTENT_RENDERER_MEDIA_MEDIA_STREAM_AUDIO_PROCESSOR_H_
+
+#include "base/atomicops.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+#include "media/base/audio_converter.h"
+#include "third_party/libjingle/source/talk/app/webrtc/mediaconstraintsinterface.h"
+#include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
+#include "third_party/webrtc/modules/interface/module_common_types.h"
+
+namespace media {
+class AudioBus;
+class AudioFifo;
+class AudioParameters;
+} // namespace media
+
+namespace webrtc {
+class AudioFrame;
+}
+
+namespace content {
+
+// This class owns an object of webrtc::AudioProcessing which contains signal
+// processing components like AGC, AEC and NS. It enables the components based
+// on the getUserMedia constraints, processes the data and outputs it in a unit
+// of 10 ms data chunk.
+class CONTENT_EXPORT MediaStreamAudioProcessor {
+ public:
+ explicit MediaStreamAudioProcessor(
+ const webrtc::MediaConstraintsInterface* constraints);
+ ~MediaStreamAudioProcessor();
+
+ // Pushes capture data in |audio_source| to the internal FIFO.
+ // Called on the capture audio thread.
+ void PushCaptureData(media::AudioBus* audio_source);
+
+ // Push the render audio to webrtc::AudioProcessing for analysis. This is
+ // needed iff echo processing is enabled.
+ // |render_audio| is the pointer to the render audio data, its format
+ // is specified by |sample_rate|, |number_of_channels| and |number_of_frames|.
+ // Called on the render audio thread.
+ void PushRenderData(const int16* render_audio,
+ int sample_rate,
+ int number_of_channels,
+ int number_of_frames,
+ base::TimeDelta render_delay);
+
+ // Processes a block of 10 ms data from the internal FIFO and outputs it via
+ // |out|. |out| is the address of the pointer that will be pointed to
+ // the post-processed data if the method is returning a true. The lifetime
+ // of the data represeted by |out| is guaranteed to outlive the method call.
+ // That also says *|out| won't change until this method is called again.
+ // Returns true if the internal FIFO has at least 10 ms data for processing,
+ // otherwise false.
+ // |capture_delay|, |volume| and |key_pressed| will be passed to
+ // webrtc::AudioProcessing to help processing the data.
+ // Called on the capture audio thread.
+ bool ProcessAndConsumeData(base::TimeDelta capture_delay,
+ int volume,
+ bool key_pressed,
+ int16** out);
+
+ // Called when the format of the capture data has changed.
+ // This has to be called before PushCaptureData() and ProcessAndConsumeData().
+ // Called on the main render thread.
+ void SetCaptureFormat(const media::AudioParameters& source_params);
+
+ // The audio format of the output from the processor.
+ const media::AudioParameters& OutputFormat() const;
+
+ // Accessor to check if the audio processing is enabled or not.
+ bool has_audio_processing() const { return audio_processing_.get() != NULL; }
+
+ private:
+ class MediaStreamAudioConverter;
+
+ // Helper to initialize the WebRtc AudioProcessing.
+ void InitializeAudioProcessingModule(
+ const webrtc::MediaConstraintsInterface* constraints);
+
+ // Helper to initialize the render converter.
+ void InitializeRenderConverterIfNeeded(int sample_rate,
+ int number_of_channels,
+ int frames_per_buffer);
+
+ // Called by ProcessAndConsumeData().
+ void ProcessData(webrtc::AudioFrame* audio_frame,
+ base::TimeDelta capture_delay,
+ int volume,
+ bool key_pressed);
+
+ // Called when the processor is going away.
+ void StopAudioProcessing();
+
+ // Cached value for the render delay latency. This member is accessed by
+ // both the capture audio thread and the render audio thread.
+ base::subtle::Atomic32 render_delay_ms_;
+
+ // webrtc::AudioProcessing module which does AEC, AGC, NS, HighPass filter,
+ // ..etc.
+ scoped_ptr<webrtc::AudioProcessing> audio_processing_;
+
+ // Converter used for the down-mixing and resampling of the capture data.
+ scoped_ptr<MediaStreamAudioConverter> capture_converter_;
+
+ // AudioFrame used to hold the output of |capture_converter_|.
+ webrtc::AudioFrame capture_frame_;
+
+ // Converter used for the down-mixing and resampling of the render data when
+ // the AEC is enabled.
+ scoped_ptr<MediaStreamAudioConverter> render_converter_;
+
+ // AudioFrame used to hold the output of |render_converter_|.
+ webrtc::AudioFrame render_frame_;
+
+ // Data bus to help converting interleaved data to an AudioBus.
+ scoped_ptr<media::AudioBus> render_data_bus_;
+
+ // Used to DCHECK that some methods are called on the main render thread.
+ base::ThreadChecker main_thread_checker_;
+
+ // Used to DCHECK that some methods are called on the capture audio thread.
+ base::ThreadChecker capture_thread_checker_;
+
+ // Used to DCHECK that PushRenderData() is called on the render audio thread.
+ base::ThreadChecker render_thread_checker_;
+};
+
+} // namespace content
+
+#endif // CONTENT_RENDERER_MEDIA_MEDIA_STREAM_AUDIO_PROCESSOR_H_
diff --git a/content/renderer/media/media_stream_audio_processor_options.cc b/content/renderer/media/media_stream_audio_processor_options.cc
new file mode 100644
index 0000000..add7f957
--- /dev/null
+++ b/content/renderer/media/media_stream_audio_processor_options.cc
@@ -0,0 +1,96 @@
+// Copyright 2013 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 "content/renderer/media/media_stream_audio_processor_options.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/libjingle/source/talk/app/webrtc/mediaconstraintsinterface.h"
+#include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
+
+namespace content {
+
+bool GetPropertyFromConstraints(const MediaConstraintsInterface* constraints,
+ const std::string& key) {
+ bool value = false;
+ return webrtc::FindConstraint(constraints, key, &value, NULL) && value;
+}
+
+void EnableEchoCancellation(AudioProcessing* audio_processing) {
+#if defined(OS_IOS)
+ // On iOS, VPIO provides built-in EC and AGC.
+ return;
+#elif defined(OS_ANDROID)
+ // Mobile devices are using AECM.
+ int err = audio_processing->echo_control_mobile()->Enable(true);
+ err |= audio_processing->echo_control_mobile()->set_routing_mode(
+ webrtc::EchoControlMobile::kSpeakerphone);
+ CHECK_EQ(err, 0);
+#else
+ int err = audio_processing->echo_cancellation()->Enable(true);
+ err |= audio_processing->echo_cancellation()->set_suppression_level(
+ webrtc::EchoCancellation::kHighSuppression);
+
+ // Enable the metrics for AEC.
+ err |= audio_processing->echo_cancellation()->enable_metrics(true);
+ err |= audio_processing->echo_cancellation()->enable_delay_logging(true);
+ CHECK_EQ(err, 0);
+#endif
+}
+
+void EnableNoiseSuppression(AudioProcessing* audio_processing) {
+ int err = audio_processing->noise_suppression()->set_level(
+ webrtc::NoiseSuppression::kHigh);
+ err |= audio_processing->noise_suppression()->Enable(true);
+ CHECK_EQ(err, 0);
+}
+
+void EnableHighPassFilter(AudioProcessing* audio_processing) {
+ CHECK_EQ(audio_processing->high_pass_filter()->Enable(true), 0);
+}
+
+// TODO(xians): stereo swapping
+void EnableTypingDetection(AudioProcessing* audio_processing) {
+ int err = audio_processing->voice_detection()->Enable(true);
+ err |= audio_processing->voice_detection()->set_likelihood(
+ webrtc::VoiceDetection::kVeryLowLikelihood);
+ CHECK_EQ(err, 0);
+}
+
+void EnableExperimentalEchoCancellation(AudioProcessing* audio_processing) {
+ webrtc::Config config;
+ config.Set<webrtc::DelayCorrection>(new webrtc::DelayCorrection(true));
+ audio_processing->SetExtraOptions(config);
+}
+
+void StartAecDump(AudioProcessing* audio_processing) {
+ // TODO(grunell): Figure out a more suitable directory for the audio dump
+ // data.
+ base::FilePath path;
+#if defined(CHROMEOS)
+ PathService::Get(base::DIR_TEMP, &path);
+#elif defined(ANDROID)
+ path = base::FilePath(FILE_PATH_LITERAL("sdcard"));
+#else
+ PathService::Get(base::DIR_EXE, &path);
+#endif
+ base::FilePath file = path.Append(FILE_PATH_LITERAL("audio.aecdump"));
+
+#if defined(OS_WIN)
+ const std::string file_name = WideToUTF8(file.value());
+#else
+ const std::string file_name = file.value();
+#endif
+ if (audio_processing->StartDebugRecording(file_name.c_str()))
+ DLOG(ERROR) << "Fail to start AEC debug recording";
+}
+
+void StopAecDump(AudioProcessing* audio_processing) {
+ if (audio_processing->StopDebugRecording())
+ DLOG(ERROR) << "Fail to stop AEC debug recording";
+}
+
+} // namespace content
diff --git a/content/renderer/media/media_stream_audio_processor_options.h b/content/renderer/media/media_stream_audio_processor_options.h
new file mode 100644
index 0000000..dcdec4e
--- /dev/null
+++ b/content/renderer/media/media_stream_audio_processor_options.h
@@ -0,0 +1,53 @@
+// Copyright 2013 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 CONTENT_RENDERER_MEDIA_MEDIA_STREAM_AUDIO_PROCESSOR_OPTIONS_H_
+#define CONTENT_RENDERER_MEDIA_MEDIA_STREAM_AUDIO_PROCESSOR_OPTIONS_H_
+
+#include <string>
+
+namespace webrtc {
+
+class AudioFrame;
+class AudioProcessing;
+class MediaConstraintsInterface;
+
+}
+
+namespace content {
+
+using webrtc::AudioProcessing;
+using webrtc::MediaConstraintsInterface;
+
+// Gets the property named by |key| from the |constraints|.
+// Returns true if the key is found and has a valid boolean value; Otherwise
+// false.
+bool GetPropertyFromConstraints(
+ const MediaConstraintsInterface* constraints,
+ const std::string& key);
+
+// Enables the echo cancellation in |audio_processing|.
+void EnableEchoCancellation(AudioProcessing* audio_processing);
+
+// Enables the noise suppression in |audio_processing|.
+void EnableNoiseSuppression(AudioProcessing* audio_processing);
+
+// Enables the high pass filter in |audio_processing|.
+void EnableHighPassFilter(AudioProcessing* audio_processing);
+
+// Enables the typing detection in |audio_processing|.
+void EnableTypingDetection(AudioProcessing* audio_processing);
+
+// Enables the experimental echo cancellation in |audio_processing|.
+void EnableExperimentalEchoCancellation(AudioProcessing* audio_processing);
+
+// Starts the echo cancellation dump in |audio_processing|.
+void StartAecDump(AudioProcessing* audio_processing);
+
+// Stops the echo cancellation dump in |audio_processing|.
+void StopAecDump(AudioProcessing* audio_processing);
+
+} // namespace content
+
+#endif // CONTENT_RENDERER_MEDIA_MEDIA_STREAM_AUDIO_PROCESSOR_OPTIONS_H_
diff --git a/content/renderer/media/media_stream_audio_processor_unittest.cc b/content/renderer/media/media_stream_audio_processor_unittest.cc
new file mode 100644
index 0000000..2d20ded
--- /dev/null
+++ b/content/renderer/media/media_stream_audio_processor_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright 2013 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/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/time/time.h"
+#include "content/public/common/content_switches.h"
+#include "content/renderer/media/media_stream_audio_processor.h"
+#include "content/renderer/media/rtc_media_constraints.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/audio_bus.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libjingle/source/talk/app/webrtc/mediastreaminterface.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Return;
+
+namespace content {
+
+namespace {
+
+#if defined(ANDROID)
+const int kAudioProcessingSampleRate = 16000;
+#else
+const int kAudioProcessingSampleRate = 32000;
+#endif
+const int kAudioProcessingNumberOfChannel = 1;
+
+// The number of packers used for testing.
+const int kNumberOfPacketsForTest = 100;
+
+void ReadDataFromSpeechFile(char* data, int length) {
+ base::FilePath file;
+ CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file));
+ file = file.Append(FILE_PATH_LITERAL("media"))
+ .Append(FILE_PATH_LITERAL("test"))
+ .Append(FILE_PATH_LITERAL("data"))
+ .Append(FILE_PATH_LITERAL("speech_16b_stereo_48kHz.raw"));
+ DCHECK(base::PathExists(file));
+ int64 data_file_size64 = 0;
+ DCHECK(file_util::GetFileSize(file, &data_file_size64));
+ EXPECT_EQ(length, file_util::ReadFile(file, data, length));
+ DCHECK(data_file_size64 > length);
+}
+
+void ApplyFixedAudioConstraints(RTCMediaConstraints* constraints) {
+ // Constant constraint keys which enables default audio constraints on
+ // mediastreams with audio.
+ struct {
+ const char* key;
+ const char* value;
+ } static const kDefaultAudioConstraints[] = {
+ { webrtc::MediaConstraintsInterface::kEchoCancellation,
+ webrtc::MediaConstraintsInterface::kValueTrue },
+ #if defined(OS_CHROMEOS) || defined(OS_MACOSX)
+ // Enable the extended filter mode AEC on platforms with known echo issues.
+ { webrtc::MediaConstraintsInterface::kExperimentalEchoCancellation,
+ webrtc::MediaConstraintsInterface::kValueTrue },
+ #endif
+ { webrtc::MediaConstraintsInterface::kAutoGainControl,
+ webrtc::MediaConstraintsInterface::kValueTrue },
+ { webrtc::MediaConstraintsInterface::kExperimentalAutoGainControl,
+ webrtc::MediaConstraintsInterface::kValueTrue },
+ { webrtc::MediaConstraintsInterface::kNoiseSuppression,
+ webrtc::MediaConstraintsInterface::kValueTrue },
+ { webrtc::MediaConstraintsInterface::kHighpassFilter,
+ webrtc::MediaConstraintsInterface::kValueTrue },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDefaultAudioConstraints); ++i) {
+ constraints->AddMandatory(kDefaultAudioConstraints[i].key,
+ kDefaultAudioConstraints[i].value, false);
+ }
+}
+
+} // namespace
+
+class MediaStreamAudioProcessorTest : public ::testing::Test {
+ public:
+ MediaStreamAudioProcessorTest()
+ : params_(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_STEREO, 48000, 16, 512) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableAudioTrackProcessing);
+ }
+
+ protected:
+ // Helper method to save duplicated code.
+ void ProcessDataAndVerifyFormat(MediaStreamAudioProcessor* audio_processor,
+ int expected_output_sample_rate,
+ int expected_output_channels,
+ int expected_output_buffer_size) {
+ // Read the audio data from a file.
+ const int packet_size =
+ params_.frames_per_buffer() * 2 * params_.channels();
+ const size_t length = packet_size * kNumberOfPacketsForTest;
+ scoped_ptr<char[]> capture_data(new char[length]);
+ ReadDataFromSpeechFile(capture_data.get(), length);
+ const int16* data_ptr = reinterpret_cast<const int16*>(capture_data.get());
+ scoped_ptr<media::AudioBus> data_bus = media::AudioBus::Create(
+ params_.channels(), params_.frames_per_buffer());
+ for (int i = 0; i < kNumberOfPacketsForTest; ++i) {
+ data_bus->FromInterleaved(data_ptr, data_bus->frames(), 2);
+ audio_processor->PushCaptureData(data_bus.get());
+
+ // |audio_processor| does nothing when the audio processing is off in
+ // the processor.
+ audio_processor->PushRenderData(
+ data_ptr,
+ params_.sample_rate(), params_.channels(),
+ params_.frames_per_buffer(), base::TimeDelta::FromMilliseconds(10));
+
+ int16* output = NULL;
+ while(audio_processor->ProcessAndConsumeData(
+ base::TimeDelta::FromMilliseconds(10), 255, false, &output)) {
+ EXPECT_TRUE(output != NULL);
+ EXPECT_EQ(audio_processor->OutputFormat().sample_rate(),
+ expected_output_sample_rate);
+ EXPECT_EQ(audio_processor->OutputFormat().channels(),
+ expected_output_channels);
+ EXPECT_EQ(audio_processor->OutputFormat().frames_per_buffer(),
+ expected_output_buffer_size);
+ }
+
+ data_ptr += params_.frames_per_buffer() * params_.channels();
+ }
+ }
+
+ media::AudioParameters params_;
+};
+
+TEST_F(MediaStreamAudioProcessorTest, WithoutAudioProcessing) {
+ // Setup the audio processor with empty constraint.
+ RTCMediaConstraints constraints;
+ MediaStreamAudioProcessor audio_processor(&constraints);
+ audio_processor.SetCaptureFormat(params_);
+ EXPECT_FALSE(audio_processor.has_audio_processing());
+
+ ProcessDataAndVerifyFormat(&audio_processor,
+ params_.sample_rate(),
+ params_.channels(),
+ params_.sample_rate() / 100);
+}
+
+TEST_F(MediaStreamAudioProcessorTest, WithAudioProcessing) {
+ // Setup the audio processor with default constraint.
+ RTCMediaConstraints constraints;
+ ApplyFixedAudioConstraints(&constraints);
+ MediaStreamAudioProcessor audio_processor(&constraints);
+ audio_processor.SetCaptureFormat(params_);
+ EXPECT_TRUE(audio_processor.has_audio_processing());
+
+ ProcessDataAndVerifyFormat(&audio_processor,
+ kAudioProcessingSampleRate,
+ kAudioProcessingNumberOfChannel,
+ kAudioProcessingSampleRate / 100);
+}
+
+} // namespace content