diff options
author | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-23 05:24:46 +0000 |
---|---|---|
committer | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-23 05:24:46 +0000 |
commit | 68c550cfef163553d89a1d0914b2e9049fd30cee (patch) | |
tree | 66757ea547fb026613360f36dca86f13adc2eb52 /media | |
parent | 85c8404778eca59eabcb4211a4a1f08e431b6852 (diff) | |
download | chromium_src-68c550cfef163553d89a1d0914b2e9049fd30cee.zip chromium_src-68c550cfef163553d89a1d0914b2e9049fd30cee.tar.gz chromium_src-68c550cfef163553d89a1d0914b2e9049fd30cee.tar.bz2 |
Replace AudioSilenceDetector with an AudioPowerMonitor.
This will allow the tab audio UI indicator to animate/paint based on the real-time measured power level of the audio stream being played. This change includes necessary plumbing of the power measurement value all the way from audio internals to the fringes of the tab UI.
BUG=178934
Review URL: https://chromiumcodereview.appspot.com/14600025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@213042 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_output_controller.cc | 49 | ||||
-rw-r--r-- | media/audio/audio_output_controller.h | 12 | ||||
-rw-r--r-- | media/audio/audio_output_controller_unittest.cc | 6 | ||||
-rw-r--r-- | media/audio/audio_power_monitor.cc | 107 | ||||
-rw-r--r-- | media/audio/audio_power_monitor.h | 96 | ||||
-rw-r--r-- | media/audio/audio_power_monitor_unittest.cc | 310 | ||||
-rw-r--r-- | media/audio/audio_silence_detector.cc | 134 | ||||
-rw-r--r-- | media/audio/audio_silence_detector.h | 112 | ||||
-rw-r--r-- | media/audio/audio_silence_detector_unittest.cc | 227 | ||||
-rw-r--r-- | media/media.gyp | 6 |
10 files changed, 555 insertions, 504 deletions
diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc index 915ca35..4005047 100644 --- a/media/audio/audio_output_controller.cc +++ b/media/audio/audio_output_controller.cc @@ -11,7 +11,7 @@ #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "build/build_config.h" -#include "media/audio/audio_silence_detector.h" +#include "media/audio/audio_power_monitor.h" #include "media/audio/audio_util.h" #include "media/audio/shared_memory_util.h" #include "media/base/scoped_histogram_timer.h" @@ -21,16 +21,13 @@ using base::TimeDelta; namespace media { -// Amount of contiguous time where all audio is silent before considering the -// stream to have transitioned and EventHandler::OnAudible() should be called. -static const int kQuestionableSilencePeriodMillis = 50; +// Time constant for AudioPowerMonitor. See AudioPowerMonitor ctor comments for +// semantics. This value was arbitrarily chosen, but seems to work well. +static const int kPowerMeasurementTimeConstantMillis = 10; -// Sample value range below which audio is considered indistinguishably silent. -// -// TODO(miu): This value should be specified in dbFS units rather than full -// scale. See TODO in audio_silence_detector.h. -static const float kIndistinguishableSilenceThreshold = - 1.0f / 4096.0f; // Note: This is approximately -72 dbFS. +// Desired frequency of calls to EventHandler::OnPowerMeasured() for reporting +// power levels in the audio signal. +static const int kPowerMeasurementsPerSecond = 30; // Polling-related constants. const int AudioOutputController::kPollNumAttempts = 3; @@ -158,20 +155,24 @@ void AudioOutputController::DoPlay() { sync_reader_->UpdatePendingBytes(0); state_ = kPlaying; - silence_detector_.reset(new AudioSilenceDetector( + + // Start monitoring power levels and send an initial notification that we're + // starting in silence. + handler_->OnPowerMeasured(AudioPowerMonitor::zero_power(), false); + power_monitor_callback_.Reset( + base::Bind(&EventHandler::OnPowerMeasured, base::Unretained(handler_))); + power_monitor_.reset(new AudioPowerMonitor( params_.sample_rate(), - TimeDelta::FromMilliseconds(kQuestionableSilencePeriodMillis), - kIndistinguishableSilenceThreshold)); + TimeDelta::FromMilliseconds(kPowerMeasurementTimeConstantMillis), + TimeDelta::FromSeconds(1) / kPowerMeasurementsPerSecond, + base::MessageLoop::current(), + power_monitor_callback_.callback())); // We start the AudioOutputStream lazily. AllowEntryToOnMoreIOData(); stream_->Start(this); - // Tell the event handler that we are now playing, and also start the silence - // detection notifications. handler_->OnPlaying(); - silence_detector_->Start( - base::Bind(&EventHandler::OnAudible, base::Unretained(handler_))); } void AudioOutputController::StopStream() { @@ -180,8 +181,13 @@ void AudioOutputController::StopStream() { if (state_ == kPlaying) { stream_->Stop(); DisallowEntryToOnMoreIOData(); - silence_detector_->Stop(true); - silence_detector_.reset(); + + // Stop monitoring power levels. By canceling power_monitor_callback_, any + // tasks posted to |message_loop_| by AudioPowerMonitor during the + // stream_->Stop() call above will not run. + power_monitor_.reset(); + power_monitor_callback_.Cancel(); + state_ = kPaused; } } @@ -198,6 +204,9 @@ void AudioOutputController::DoPause() { // Send a special pause mark to the low-latency audio thread. sync_reader_->UpdatePendingBytes(kPauseMark); + // Paused means silence follows. + handler_->OnPowerMeasured(AudioPowerMonitor::zero_power(), false); + handler_->OnPaused(); } @@ -270,7 +279,7 @@ int AudioOutputController::OnMoreIOData(AudioBus* source, sync_reader_->UpdatePendingBytes( buffers_state.total_bytes() + frames * params_.GetBytesPerFrame()); - silence_detector_->Scan(dest, frames); + power_monitor_->Scan(*dest, frames); AllowEntryToOnMoreIOData(); return frames; diff --git a/media/audio/audio_output_controller.h b/media/audio/audio_output_controller.h index dddb90e..5182453 100644 --- a/media/audio/audio_output_controller.h +++ b/media/audio/audio_output_controller.h @@ -7,6 +7,7 @@ #include "base/atomic_ref_count.h" #include "base/callback.h" +#include "base/cancelable_callback.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/timer/timer.h" @@ -52,7 +53,7 @@ namespace media { -class AudioSilenceDetector; +class AudioPowerMonitor; class MEDIA_EXPORT AudioOutputController : public base::RefCountedThreadSafe<AudioOutputController>, @@ -66,7 +67,7 @@ class MEDIA_EXPORT AudioOutputController public: virtual void OnCreated() = 0; virtual void OnPlaying() = 0; - virtual void OnAudible(bool is_audible) = 0; + virtual void OnPowerMeasured(float power_dbfs, bool clipped) = 0; virtual void OnPaused() = 0; virtual void OnError() = 0; virtual void OnDeviceChange(int new_buffer_size, int new_sample_rate) = 0; @@ -234,9 +235,10 @@ class MEDIA_EXPORT AudioOutputController int number_polling_attempts_left_; // Scans audio samples from OnMoreIOData() as input and causes - // EventHandler::OnAudbile() to be called whenever a transition to a period of - // silence or non-silence is detected. - scoped_ptr<AudioSilenceDetector> silence_detector_; + // EventHandler::OnPowerMeasured() to be called with power level measurements + // at regular intervals. + scoped_ptr<AudioPowerMonitor> power_monitor_; + base::CancelableCallback<void(float, bool)> power_monitor_callback_; DISALLOW_COPY_AND_ASSIGN(AudioOutputController); }; diff --git a/media/audio/audio_output_controller_unittest.cc b/media/audio/audio_output_controller_unittest.cc index 51138b1..128cc07 100644 --- a/media/audio/audio_output_controller_unittest.cc +++ b/media/audio/audio_output_controller_unittest.cc @@ -40,7 +40,7 @@ class MockAudioOutputControllerEventHandler MOCK_METHOD0(OnCreated, void()); MOCK_METHOD0(OnPlaying, void()); - MOCK_METHOD1(OnAudible, void(bool is_audible)); + MOCK_METHOD2(OnPowerMeasured, void(float power_dbfs, bool clipped)); MOCK_METHOD0(OnPaused, void()); MOCK_METHOD0(OnError, void()); MOCK_METHOD2(OnDeviceChange, void(int new_buffer_size, int new_sample_rate)); @@ -131,10 +131,10 @@ class AudioOutputControllerTest : public testing::Test { void Play() { // Expect the event handler to receive one OnPlaying() call and one or more - // OnAudible() calls. + // OnPowerMeasured() calls. EXPECT_CALL(mock_event_handler_, OnPlaying()) .WillOnce(SignalEvent(&play_event_)); - EXPECT_CALL(mock_event_handler_, OnAudible(_)) + EXPECT_CALL(mock_event_handler_, OnPowerMeasured(_, false)) .Times(AtLeast(1)); // During playback, the mock pretends to provide audio data rendered and diff --git a/media/audio/audio_power_monitor.cc b/media/audio/audio_power_monitor.cc new file mode 100644 index 0000000..4dc1517 --- /dev/null +++ b/media/audio/audio_power_monitor.cc @@ -0,0 +1,107 @@ +// 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 "media/audio/audio_power_monitor.h" + +#include <algorithm> +#include <cmath> + +#include "base/bind.h" +#include "base/float_util.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "media/base/audio_bus.h" + +namespace media { + +AudioPowerMonitor::AudioPowerMonitor( + int sample_rate, + const base::TimeDelta& time_constant, + const base::TimeDelta& measurement_period, + base::MessageLoop* message_loop, + const PowerMeasurementCallback& callback) + : sample_weight_( + 1.0f - expf(-1.0f / (sample_rate * time_constant.InSecondsF()))), + num_frames_per_callback_(sample_rate * measurement_period.InSecondsF()), + message_loop_(message_loop), + power_level_callback_(callback), + average_power_(0.0f), + clipped_since_last_notification_(false), + frames_since_last_notification_(0), + last_reported_power_(-1.0f), + last_reported_clipped_(false) { + DCHECK(message_loop_); + DCHECK(!power_level_callback_.is_null()); +} + +AudioPowerMonitor::~AudioPowerMonitor() { +} + +void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) { + DCHECK_LE(num_frames, buffer.frames()); + const int num_channels = buffer.channels(); + if (num_frames <= 0 || num_channels <= 0) + return; + + // Calculate a new average power by applying a first-order low-pass filter + // over the audio samples in |buffer|. + // + // TODO(miu): Implement optimized SSE/NEON to more efficiently compute the + // results (in media/base/vector_math) in soon-upcoming change. + bool clipped = false; + float sum_power = 0.0f; + for (int i = 0; i < num_channels; ++i) { + float average_power_this_channel = average_power_; + const float* p = buffer.channel(i); + const float* const end_of_samples = p + num_frames; + for (; p < end_of_samples; ++p) { + const float sample = *p; + const float sample_squared = sample * sample; + clipped |= (sample_squared > 1.0f); + average_power_this_channel += + (sample_squared - average_power_this_channel) * sample_weight_; + } + // If data in audio buffer is garbage, ignore its effect on the result. + if (base::IsNaN(average_power_this_channel)) + average_power_this_channel = average_power_; + sum_power += average_power_this_channel; + } + + // Update accumulated results. + average_power_ = std::max(0.0f, std::min(1.0f, sum_power / num_channels)); + clipped_since_last_notification_ |= clipped; + frames_since_last_notification_ += num_frames; + + // Once enough frames have been scanned, report the accumulated results. + if (frames_since_last_notification_ >= num_frames_per_callback_) { + // Note: Forgo making redundant callbacks when results remain unchanged. + // Part of this is to pin-down the power to zero if it is insignificantly + // small. + const float kInsignificantPower = 1.0e-10f; // -100 dBFS + const float power = + (average_power_ < kInsignificantPower) ? 0.0f : average_power_; + if (power != last_reported_power_ || + clipped_since_last_notification_ != last_reported_clipped_) { + const float power_dbfs = 10.0f * log10f(power); + // Try to post a task to run the callback with the dBFS result. The + // posting of the task is guaranteed to be non-blocking, and therefore + // could fail. However, in the common case, failures should be rare (and + // then the task-post will likely succeed the next time it's attempted). + if (!message_loop_->TryPostTask( + FROM_HERE, + base::Bind(power_level_callback_, + power_dbfs, clipped_since_last_notification_))) { + DVLOG(2) << "TryPostTask() did not succeed."; + return; + } + last_reported_power_ = power; + last_reported_clipped_ = clipped_since_last_notification_; + } + clipped_since_last_notification_ = false; + frames_since_last_notification_ = 0; + } +} + +} // namespace media diff --git a/media/audio/audio_power_monitor.h b/media/audio/audio_power_monitor.h new file mode 100644 index 0000000..b65fb13 --- /dev/null +++ b/media/audio/audio_power_monitor.h @@ -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. + +#ifndef MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_ +#define MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_ + +#include <limits> + +#include "base/callback.h" +#include "media/base/media_export.h" + +// An audio signal power monitor. It is periodically provided an AudioBus by +// the native audio thread, and the audio samples in each channel are analyzed +// to determine the average power of the signal over a time period. Here +// "average power" is a running average calculated by using a first-order +// low-pass filter over the square of the samples scanned. Whenever reporting +// the power level, this running average is converted to dBFS (decibels relative +// to full-scale) units. +// +// Note that extreme care has been taken to make the AudioPowerMonitor::Scan() +// method safe to be called on the native audio thread. The code acquires no +// locks, nor engages in any operation that could result in an +// undetermined/unbounded amount of run-time. + +namespace base { +class MessageLoop; +class TimeDelta; +} + +namespace media { + +class AudioBus; + +class MEDIA_EXPORT AudioPowerMonitor { + public: + // Reports power level in terms of dBFS (see zero_power() and max_power() + // below). |clipped| is true if any *one* sample exceeded maximum amplitude + // since the last invocation. + typedef base::Callback<void(float power_dbfs, bool clipped)> + PowerMeasurementCallback; + + // |sample_rate| is the audio signal sample rate (Hz). |time_constant| + // characterizes how samples are averaged over time to determine the power + // level; and is the amount of time it takes a zero power level to increase to + // ~63.2% of maximum given a step input signal. |measurement_period| is the + // time length of signal to analyze before invoking the callback to report the + // current power level. |message_loop| is where the |callback| task will be + // posted. + AudioPowerMonitor(int sample_rate, + const base::TimeDelta& time_constant, + const base::TimeDelta& measurement_period, + base::MessageLoop* message_loop, + const PowerMeasurementCallback& callback); + + ~AudioPowerMonitor(); + + // Scan more |frames| of audio data from |buffer|. It is safe to call this + // from a real-time priority thread. + void Scan(const AudioBus& buffer, int frames); + + // dBFS value corresponding to zero power in the audio signal. + static float zero_power() { return -std::numeric_limits<float>::infinity(); } + + // dBFS value corresponding to maximum power in the audio signal. + static float max_power() { return 0.0f; } + + private: + // The weight applied when averaging-in each sample. Computed from the + // |sample_rate| and |time_constant|. + const float sample_weight_; + + // Number of audio frames to be scanned before reporting the current power + // level via callback, as computed from |sample_rate| and + // |measurement_period|. + const int num_frames_per_callback_; + + // MessageLoop and callback used to notify of the current power level. + base::MessageLoop* const message_loop_; + const PowerMeasurementCallback power_level_callback_; + + // Accumulated results over one or more calls to Scan(). + float average_power_; + bool clipped_since_last_notification_; + int frames_since_last_notification_; + + // Keep track of last reported results to forgo making redundant callbacks. + float last_reported_power_; + bool last_reported_clipped_; + + DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitor); +}; + +} // namespace media + +#endif // MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_ diff --git a/media/audio/audio_power_monitor_unittest.cc b/media/audio/audio_power_monitor_unittest.cc new file mode 100644 index 0000000..baea139 --- /dev/null +++ b/media/audio/audio_power_monitor_unittest.cc @@ -0,0 +1,310 @@ +// 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 "media/audio/audio_power_monitor.h" + +#include <limits> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "media/base/audio_bus.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static const int kSampleRate = 48000; +static const int kFramesPerBuffer = 128; + +static const int kTimeConstantMillis = 5; +static const int kMeasurementPeriodMillis = 20; + +namespace { + +// Container for each parameterized test's data (input and expected results). +class TestScenario { + public: + TestScenario(const float* data, int num_channels, int num_frames, + float expected_power, bool expected_clipped) + : expected_power_(expected_power), expected_clipped_(expected_clipped) { + CreatePopulatedBuffer(data, num_channels, num_frames); + } + + // Copy constructor and assignment operator for ::testing::Values(...). + TestScenario(const TestScenario& other) { *this = other; } + TestScenario& operator=(const TestScenario& other) { + this->expected_power_ = other.expected_power_; + this->expected_clipped_ = other.expected_clipped_; + this->bus_ = AudioBus::Create(other.bus_->channels(), other.bus_->frames()); + other.bus_->CopyTo(this->bus_.get()); + return *this; + } + + // Returns this TestScenario, but with a bad sample value placed in the middle + // of channel 0. + TestScenario WithABadSample(float bad_value) const { + TestScenario result(*this); + result.bus_->channel(0)[result.bus_->frames() / 2] = bad_value; + return result; + } + + const AudioBus& data() const { + return *bus_; + } + + float expected_power() const { + return expected_power_; + } + + bool expected_clipped() const { + return expected_clipped_; + } + + private: + // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of + // data. The given test |data| is repeated to fill the buffer. + void CreatePopulatedBuffer( + const float* data, int num_channels, int num_frames) { + bus_ = AudioBus::Create(num_channels, kFramesPerBuffer); + for (int ch = 0; ch < num_channels; ++ch) { + for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) { + const int num_to_copy = std::min(num_frames, kFramesPerBuffer - frames); + memcpy(bus_->channel(ch) + frames, data + num_frames * ch, + sizeof(float) * num_to_copy); + } + } + } + + float expected_power_; + bool expected_clipped_; + scoped_ptr<AudioBus> bus_; +}; + +// An observer that receives power measurements. Each power measurement should +// should make progress towards the goal value. +class MeasurementObserver { + public: + MeasurementObserver(float goal_power_measurement, bool goal_clipped) + : goal_power_measurement_(goal_power_measurement), + goal_clipped_(goal_clipped), measurement_count_(0), + last_power_measurement_(AudioPowerMonitor::zero_power()), + last_clipped_(false) {} + + int measurement_count() const { + return measurement_count_; + } + + float last_power_measurement() const { + return last_power_measurement_; + } + + bool last_clipped() const { + return last_clipped_; + } + + void OnPowerMeasured(float cur_power_measurement, bool clipped) { + if (measurement_count_ == 0) { + measurements_should_increase_ = + (cur_power_measurement < goal_power_measurement_); + } else { + SCOPED_TRACE(::testing::Message() + << "Power: goal=" << goal_power_measurement_ + << "; last=" << last_power_measurement_ + << "; cur=" << cur_power_measurement); + + if (last_power_measurement_ != goal_power_measurement_) { + if (measurements_should_increase_) { + EXPECT_LE(last_power_measurement_, cur_power_measurement) + << "Measurements should be monotonically increasing."; + } else { + EXPECT_GE(last_power_measurement_, cur_power_measurement) + << "Measurements should be monotonically decreasing."; + } + } else { + EXPECT_EQ(last_power_measurement_, cur_power_measurement) + << "Measurements are numerically unstable at goal value."; + } + } + + last_power_measurement_ = cur_power_measurement; + last_clipped_ = clipped; + ++measurement_count_; + } + + private: + const float goal_power_measurement_; + const bool goal_clipped_; + int measurement_count_; + bool measurements_should_increase_; + float last_power_measurement_; + bool last_clipped_; + + DISALLOW_COPY_AND_ASSIGN(MeasurementObserver); +}; + +} // namespace + +class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> { + public: + AudioPowerMonitorTest() + : power_monitor_( + kSampleRate, + base::TimeDelta::FromMilliseconds(kTimeConstantMillis), + base::TimeDelta::FromMilliseconds(kMeasurementPeriodMillis), + &message_loop_, + base::Bind(&AudioPowerMonitorTest::OnPowerMeasured, + base::Unretained(this))) {} + + void FeedAndCheckExpectedPowerIsMeasured( + const AudioBus& bus, float power, bool clipped) { + // Feed the AudioPowerMonitor. It should post tasks to |message_loop_|. + static const int kNumFeedIters = 100; + for (int i = 0; i < kNumFeedIters; ++i) + power_monitor_.Scan(bus, bus.frames()); + + // Set up an observer and run all the enqueued tasks. + MeasurementObserver observer(power, clipped); + current_observer_ = &observer; + message_loop_.RunUntilIdle(); + current_observer_ = NULL; + + // Check that the results recorded by the observer are the same whole-number + // dBFS. + EXPECT_EQ(static_cast<int>(power), + static_cast<int>(observer.last_power_measurement())); + EXPECT_EQ(clipped, observer.last_clipped()); + } + + private: + void OnPowerMeasured(float power, bool clipped) { + CHECK(current_observer_); + current_observer_->OnPowerMeasured(power, clipped); + } + + base::MessageLoop message_loop_; + AudioPowerMonitor power_monitor_; + MeasurementObserver* current_observer_; + + DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest); +}; + +TEST_P(AudioPowerMonitorTest, MeasuresPowerOfSignal) { + const TestScenario& scenario = GetParam(); + + scoped_ptr<AudioBus> zeroed_bus = + AudioBus::Create(scenario.data().channels(), scenario.data().frames()); + zeroed_bus->Zero(); + + // Send a "zero power" audio signal, then this scenario's audio signal, then + // the "zero power" audio signal again; testing that the power monitor + // measurements match expected values. + FeedAndCheckExpectedPowerIsMeasured( + *zeroed_bus, AudioPowerMonitor::zero_power(), false); + FeedAndCheckExpectedPowerIsMeasured( + scenario.data(), scenario.expected_power(), scenario.expected_clipped()); + FeedAndCheckExpectedPowerIsMeasured( + *zeroed_bus, AudioPowerMonitor::zero_power(), false); +} + +static const float kMonoSilentNoise[] = { + 0.01f, -0.01f +}; + +static const float kMonoMaxAmplitude[] = { + 1.0f +}; + +static const float kMonoMaxAmplitude2[] = { + -1.0f, 1.0f +}; + +static const float kMonoHalfMaxAmplitude[] = { + 0.5f, -0.5f, 0.5f, -0.5f +}; + +static const float kMonoAmplitudeClipped[] = { + 2.0f, -2.0f +}; + +static const float kMonoMaxAmplitudeWithClip[] = { + 2.0f, 0.0, 0.0f, 0.0f +}; + +static const float kMonoMaxAmplitudeWithClip2[] = { + 4.0f, 0.0, 0.0f, 0.0f +}; + +static const float kStereoSilentNoise[] = { + // left channel + 0.005f, -0.005f, + // right channel + 0.005f, -0.005f +}; + +static const float kStereoMaxAmplitude[] = { + // left channel + 1.0f, -1.0f, + // right channel + -1.0f, 1.0f +}; + +static const float kRightChannelMaxAmplitude[] = { + // left channel + 0.0f, 0.0f, 0.0f, 0.0f, + // right channel + -1.0f, 1.0f, -1.0f, 1.0f +}; + +static const float kLeftChannelHalfMaxAmplitude[] = { + // left channel + 0.5f, -0.5f, 0.5f, -0.5f, + // right channel + 0.0f, 0.0f, 0.0f, 0.0f, +}; + +static const float kStereoMixed[] = { + // left channel + 0.5f, -0.5f, 0.5f, -0.5f, + // right channel + -1.0f, 1.0f, -1.0f, 1.0f +}; + +static const float kStereoMixed2[] = { + // left channel + 1.0f, -1.0f, 0.75f, -0.75f, 0.5f, -0.5f, 0.25f, -0.25f, + // right channel + 0.25f, -0.25f, 0.5f, -0.5f, 0.75f, -0.75f, 1.0f, -1.0f +}; + +INSTANTIATE_TEST_CASE_P( + Scenarios, AudioPowerMonitorTest, + ::testing::Values( + TestScenario(kMonoSilentNoise, 1, 2, -40, false), + TestScenario(kMonoMaxAmplitude, 1, 1, + AudioPowerMonitor::max_power(), false), + TestScenario(kMonoMaxAmplitude2, 1, 2, + AudioPowerMonitor::max_power(), false), + TestScenario(kMonoHalfMaxAmplitude, 1, 4, -6, false), + TestScenario(kMonoAmplitudeClipped, 1, 2, + AudioPowerMonitor::max_power(), true), + TestScenario(kMonoMaxAmplitudeWithClip, 1, 4, + AudioPowerMonitor::max_power(), true), + TestScenario(kMonoMaxAmplitudeWithClip2, 1, 4, + AudioPowerMonitor::max_power(), true), + TestScenario(kMonoSilentNoise, 1, 2, + AudioPowerMonitor::zero_power(), true). + WithABadSample(std::numeric_limits<float>::infinity()), + TestScenario(kMonoHalfMaxAmplitude, 1, 4, + AudioPowerMonitor::zero_power(), false). + WithABadSample(std::numeric_limits<float>::quiet_NaN()), + TestScenario(kStereoSilentNoise, 2, 2, -46, false), + TestScenario(kStereoMaxAmplitude, 2, 2, + AudioPowerMonitor::max_power(), false), + TestScenario(kRightChannelMaxAmplitude, 2, 4, -3, false), + TestScenario(kLeftChannelHalfMaxAmplitude, 2, 4, -9, false), + TestScenario(kStereoMixed, 2, 4, -2, false), + TestScenario(kStereoMixed2, 2, 8, -3, false))); + +} // namespace media diff --git a/media/audio/audio_silence_detector.cc b/media/audio/audio_silence_detector.cc deleted file mode 100644 index a37faef..0000000 --- a/media/audio/audio_silence_detector.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 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 "media/audio/audio_silence_detector.h" - -#include "base/float_util.h" -#include "base/time/time.h" -#include "media/base/audio_bus.h" - -using base::AtomicRefCountDec; -using base::AtomicRefCountInc; -using base::AtomicRefCountIsOne; -using base::AtomicRefCountIsZero; - -namespace media { - -AudioSilenceDetector::AudioSilenceDetector( - int sample_rate, - const base::TimeDelta& questionable_silence_period, - float indistinguishable_silence_threshold) - : polling_period_(questionable_silence_period), - frames_before_observing_silence_( - sample_rate * questionable_silence_period.InSecondsF()), - silence_threshold_(indistinguishable_silence_threshold), - frames_silent_so_far_(frames_before_observing_silence_), - observing_silence_(1), - was_audible_(false) { -} - -AudioSilenceDetector::~AudioSilenceDetector() { - DCHECK(thread_checker_.CalledOnValidThread()); - // Note: If active, ~RepeatingTimer() will StopAndAbandon(). -} - -void AudioSilenceDetector::Start(const AudibleCallback& callback) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(notify_is_audible_.is_null()); - DCHECK(!callback.is_null()); - - notify_is_audible_ = callback; - was_audible_ = AtomicRefCountIsZero(&observing_silence_); - notify_is_audible_.Run(was_audible_); - poll_timer_.Start( - FROM_HERE, polling_period_, - this, &AudioSilenceDetector::MaybeInvokeAudibleCallback); -} - -void AudioSilenceDetector::Stop(bool notify_ending_in_silence) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!notify_is_audible_.is_null()); - - poll_timer_.Stop(); - if (notify_ending_in_silence) - notify_is_audible_.Run(false); - notify_is_audible_.Reset(); -} - -void AudioSilenceDetector::Scan(const AudioBus* buffer, int frames) { - // Determine whether the frames just read are probably silence. If enough - // frames of silence have been observed, flip the |observing_silence_| - // boolean, which will be read by another thread. - if (ProbablyContainsSilence(buffer, frames)) { - // Note: Prevent indefinite incrementing of |frames_silent_so_far_|, to - // avoid eventual integer overflow. - if (frames_silent_so_far_ < frames_before_observing_silence_) { - frames_silent_so_far_ += frames; - if (frames_silent_so_far_ >= frames_before_observing_silence_) { - DCHECK(AtomicRefCountIsZero(&observing_silence_)); - AtomicRefCountInc(&observing_silence_); - } - } - } else { - if (frames_silent_so_far_ >= frames_before_observing_silence_) { - DCHECK(AtomicRefCountIsOne(&observing_silence_)); - AtomicRefCountDec(&observing_silence_); - } - frames_silent_so_far_ = 0; - } -} - -void AudioSilenceDetector::MaybeInvokeAudibleCallback() { - DCHECK(thread_checker_.CalledOnValidThread()); - - const bool is_now_audible = AtomicRefCountIsZero(&observing_silence_); - if (was_audible_ && !is_now_audible) - notify_is_audible_.Run(was_audible_ = false); - else if (!was_audible_ && is_now_audible) - notify_is_audible_.Run(was_audible_ = true); -} - -bool AudioSilenceDetector::ProbablyContainsSilence(const AudioBus* buffer, - int num_frames) { - if (!buffer) - return true; - DCHECK_LE(num_frames, buffer->frames()); - if (buffer->frames() <= 0) - return true; - - // Scan the data in each channel. If any one channel contains sound whose - // range of values exceeds |silence_threshold_|, return false immediately. - for (int i = 0; i < buffer->channels(); ++i) { - // Examine the 1st, 2nd (+1), 4th (+2), 7th (+3), 11th (+4), etc. samples, - // checking whether |silence_threshold_| has been breached each time. For - // typical AudioBus sizes, this algorithm will in the worst case examine - // fewer than 10% of the samples. - // - // Note that there *is* a heavy bias in sampling at the beginning of the - // channels, but that doesn't matter. The buffer sizes are simply too - // small. For example, it is commonplace to use 128-sample buffers, which - // represents ~3 ms of audio at 44.1 kHz; and this means that frequencies - // below ~350 Hz will span more than one buffer to make a full cycle. In - // all, the algorithm here is meant to be dirt-simple math that isn't - // susceptible to periodic bias within a single buffer. - const float* p = buffer->channel(i); - const float* const end_of_samples = p + num_frames; - int skip = 1; - float min_value = *p; - float max_value = *p; - for (p += skip; p < end_of_samples; ++skip, p += skip) { - DCHECK(base::IsFinite(*p)); - if (*p < min_value) - min_value = *p; - else if (max_value < *p) - max_value = *p; - if ((max_value - min_value) > silence_threshold_) - return false; - } - } - - return true; -} - -} // namespace media diff --git a/media/audio/audio_silence_detector.h b/media/audio/audio_silence_detector.h deleted file mode 100644 index 0135a96..0000000 --- a/media/audio/audio_silence_detector.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 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 MEDIA_AUDIO_AUDIO_SILENCE_DETECTOR_H_ -#define MEDIA_AUDIO_AUDIO_SILENCE_DETECTOR_H_ - -#include "base/atomic_ref_count.h" -#include "base/callback.h" -#include "base/memory/scoped_ptr.h" -#include "base/threading/thread_checker.h" -#include "base/timer/timer.h" -#include "media/base/media_export.h" - -// An audio silence detector. It is periodically provided an AudioBus by the -// native audio thread, where simple logic determines whether the audio samples -// in each channel in the buffer represent silence. If a long-enough period of -// contiguous silence is observed in all channels, a notification callback is -// run on the thread that constructed AudioSilenceDetector. -// -// Note that extreme care has been taken to make the -// AudioSilenceDetector::Scan() method safe to be called on the native audio -// thread. The code acquires no locks, nor engages in any operation that could -// result in an undetermined/unbounded amount of run-time. Comments in -// audio_silence_detector.cc elaborate further on the silence detection -// algorithm. - -namespace base { -class TimeDelta; -} - -namespace media { - -class AudioBus; - -class MEDIA_EXPORT AudioSilenceDetector { - public: - typedef base::Callback<void(bool)> AudibleCallback; - - // Tunable parameters: |questionable_silence_period| is the amount of time - // where audio must remain silent before triggerring a callback. - // |indistinguishable_silence_threshold| is the value range below which audio - // is considered silent, in full-scale units. - // - // TODO(miu): |indistinguishable_silence_threshold| should be specified in - // dbFS units rather than full-scale. We need a dbFS data type for - // media/audio first. - AudioSilenceDetector(int sample_rate, - const base::TimeDelta& questionable_silence_period, - float indistinguishable_silence_threshold); - - ~AudioSilenceDetector(); - - // Start detecting silence, notifying via the given callback. - void Start(const AudibleCallback& notify_is_audible); - - // Stop detecting silence. If |notify_ending_in_silence| is true, a final - // notify_is_audible(false) call will be made here. - void Stop(bool notify_ending_in_silence); - - // Scan more |frames| of audio data from |buffer|. This is usually called - // within the native audio thread's "more data" callback. - void Scan(const AudioBus* buffer, int frames); - - private: - // Called by |poll_timer_| at regular intervals to determine whether to invoke - // the callback due to a silence state change. - void MaybeInvokeAudibleCallback(); - - // Returns true if the first |num_frames| frames in all channels in |buffer| - // probably contain silence. A simple heuristic is used to quickly examine a - // subset of the samples in each channel, hence the name of this method. - // "Silence" means that the range of the sample values examined does not - // exceed |silence_threshold_|. - bool ProbablyContainsSilence(const AudioBus* buffer, int num_frames); - - // Time between polls for changes in state. - const base::TimeDelta polling_period_; - - // Number of frames of contiguous silence to be observed before setting - // |observing_silence_| to false. - const int frames_before_observing_silence_; - - // Threshold below which audio should be considered indistinguishably silent. - const float silence_threshold_; - - // Number of frames of contiguous silence observed thus far on the native - // audio thread. - int frames_silent_so_far_; - - // Boolean state (0 or 1) set by the native audio thread. This is polled - // regularly by the thread that invokes the callback. - base::AtomicRefCount observing_silence_; - - // Callback for notifying of a detected transition to silence or non-silence. - AudibleCallback notify_is_audible_; - - // Last reported audible state, used for de-duping callback invocations. - bool was_audible_; - - // Fires regularly, calling MaybeInvokeAudibleCallback(). - base::RepeatingTimer<AudioSilenceDetector> poll_timer_; - - // Constructor, destructor and most methods must be called on the same thread. - base::ThreadChecker thread_checker_; - - DISALLOW_COPY_AND_ASSIGN(AudioSilenceDetector); -}; - -} // namespace media - -#endif // MEDIA_AUDIO_AUDIO_SILENCE_DETECTOR_H_ diff --git a/media/audio/audio_silence_detector_unittest.cc b/media/audio/audio_silence_detector_unittest.cc deleted file mode 100644 index 9d4f41c..0000000 --- a/media/audio/audio_silence_detector_unittest.cc +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 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 "media/audio/audio_silence_detector.h" - -#include "base/bind.h" -#include "base/bind_helpers.h" -#include "base/message_loop/message_loop.h" -#include "base/synchronization/waitable_event.h" -#include "base/threading/thread.h" -#include "base/time/time.h" -#include "media/base/audio_bus.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using ::testing::_; -using ::testing::InvokeWithoutArgs; -using ::testing::TestWithParam; -using ::testing::Values; - -namespace media { - -static const int kSampleRate = 48000; -static const int kFramesPerBuffer = 128; - -namespace { - -class MockObserver { - public: - MOCK_METHOD1(OnAudible, void(bool)); -}; - -struct TestScenario { - const float* data; - int num_channels; - int num_frames; - float value_range; - - TestScenario(const float* d, int c, int f, float v) - : data(d), num_channels(c), num_frames(f), value_range(v) {} -}; - -} // namespace - -class AudioSilenceDetectorTest : public TestWithParam<TestScenario> { - public: - AudioSilenceDetectorTest() - : audio_manager_thread_(new base::Thread("AudioThread")), - notification_received_(false, false) { - audio_manager_thread_->Start(); - audio_message_loop_ = audio_manager_thread_->message_loop_proxy(); - } - - virtual ~AudioSilenceDetectorTest() { - SyncWithAudioThread(); - } - - AudioSilenceDetector* silence_detector() const { - return silence_detector_.get(); - } - - void StartSilenceDetector(float threshold, MockObserver* observer) { - audio_message_loop_->PostTask( - FROM_HERE, - base::Bind(&AudioSilenceDetectorTest::StartDetectorOnAudioThread, - base::Unretained(this), threshold, observer)); - SyncWithAudioThread(); - } - - void StopSilenceDetector() { - audio_message_loop_->PostTask( - FROM_HERE, - base::Bind(&AudioSilenceDetectorTest::StopDetectorOnAudioThread, - base::Unretained(this))); - SyncWithAudioThread(); - } - - // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of - // data. The given test |data| is repeated to fill the buffer. - scoped_ptr<AudioBus> CreatePopulatedBuffer( - const float* data, int num_channels, int num_frames) { - scoped_ptr<AudioBus> bus = AudioBus::Create(num_channels, kFramesPerBuffer); - for (int ch = 0; ch < num_channels; ++ch) { - for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) { - const int num_to_copy = std::min(num_frames, kFramesPerBuffer - frames); - memcpy(bus->channel(ch) + frames, data + num_frames * ch, - sizeof(float) * num_to_copy); - } - } - return bus.Pass(); - } - - void SignalNotificationReceived() { - notification_received_.Signal(); - } - - void WaitForNotificationReceived() { - notification_received_.Wait(); - } - - // Post a task on the audio thread and block until it is executed. This - // provides a barrier: All previous tasks pending on the audio thread have - // completed before this method returns. - void SyncWithAudioThread() { - base::WaitableEvent done(false, false); - audio_message_loop_->PostTask( - FROM_HERE, - base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); - done.Wait(); - } - - private: - void StartDetectorOnAudioThread(float threshold, MockObserver* observer) { - const AudioSilenceDetector::AudibleCallback notify_is_audible = - base::Bind(&MockObserver::OnAudible, base::Unretained(observer)); - silence_detector_.reset(new AudioSilenceDetector( - kSampleRate, base::TimeDelta::FromMilliseconds(1), threshold)); - silence_detector_->Start(notify_is_audible); - } - - void StopDetectorOnAudioThread() { - silence_detector_->Stop(true); - silence_detector_.reset(); - } - - scoped_ptr<base::Thread> audio_manager_thread_; - scoped_refptr<base::MessageLoopProxy> audio_message_loop_; - - base::WaitableEvent notification_received_; - - scoped_ptr<AudioSilenceDetector> silence_detector_; - - DISALLOW_COPY_AND_ASSIGN(AudioSilenceDetectorTest); -}; - -TEST_P(AudioSilenceDetectorTest, TriggersAtVariousThresholds) { - static const float kThresholdsToTry[] = - { 0.0f, 0.125f, 0.25f, 0.5f, 0.75f, 1.0f }; - - const TestScenario& scenario = GetParam(); - - for (size_t i = 0; i < arraysize(kThresholdsToTry); ++i) { - MockObserver observer; - - // Start the detector. This should cause a single callback to indicate - // we're starting out in silence. - EXPECT_CALL(observer, OnAudible(false)) - .WillOnce(InvokeWithoutArgs( - this, &AudioSilenceDetectorTest::SignalNotificationReceived)) - .RetiresOnSaturation(); - StartSilenceDetector(kThresholdsToTry[i], &observer); - WaitForNotificationReceived(); - - // Send more data to the silence detector. For some test scenarios, the - // sound data will trigger a callback. - const bool expect_a_callback = (kThresholdsToTry[i] < scenario.value_range); - if (expect_a_callback) { - EXPECT_CALL(observer, OnAudible(true)) - .WillOnce(InvokeWithoutArgs( - this, &AudioSilenceDetectorTest::SignalNotificationReceived)) - .RetiresOnSaturation(); - } - scoped_ptr<AudioBus> bus = CreatePopulatedBuffer( - scenario.data, scenario.num_channels, scenario.num_frames); - silence_detector()->Scan(bus.get(), bus->frames()); - if (expect_a_callback) - WaitForNotificationReceived(); - - // Stop the detector. This should cause another callback to indicate we're - // ending in silence. - EXPECT_CALL(observer, OnAudible(false)) - .WillOnce(InvokeWithoutArgs( - this, &AudioSilenceDetectorTest::SignalNotificationReceived)) - .RetiresOnSaturation(); - StopSilenceDetector(); - WaitForNotificationReceived(); - - SyncWithAudioThread(); - } -} - -static const float kAllZeros[] = { - // left channel - 0.0f, - // right channel - 0.0f -}; - -static const float kAllOnes[] = { - // left channel - 1.0f, - // right channel - 1.0f -}; - -static const float kSilentLeftChannel[] = { - // left channel - 0.5f, 0.5f, 0.5f, 0.5f, - // right channel - 0.0f, 0.25f, 0.0f, 0.0f -}; - -static const float kSilentRightChannel[] = { - // left channel - 1.0f, 1.0f, 0.75f, 0.5f, 1.0f, - // right channel - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f -}; - -static const float kAtDifferentVolumesAndBias[] = { - // left channel - 1.0f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, - // right channel - 0.0f, 0.2f, 0.4f, 0.6f, 0.4f, 0.2f, 0.0f, 0.2f, 0.4f, 0.6f, 0.4f, 0.2f -}; - -INSTANTIATE_TEST_CASE_P( - Scenarios, AudioSilenceDetectorTest, - Values( - TestScenario(kAllZeros, 2, 1, 0.0f), - TestScenario(kAllOnes, 2, 1, 0.0f), - TestScenario(kSilentLeftChannel, 2, 4, 0.25f), - TestScenario(kSilentRightChannel, 2, 5, 0.5f), - TestScenario(kAtDifferentVolumesAndBias, 2, 12, 0.6f))); - -} // namespace media diff --git a/media/media.gyp b/media/media.gyp index e41f995..fb681f9 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -94,8 +94,8 @@ 'audio/audio_output_proxy.h', 'audio/audio_output_resampler.cc', 'audio/audio_output_resampler.h', - 'audio/audio_silence_detector.cc', - 'audio/audio_silence_detector.h', + 'audio/audio_power_monitor.cc', + 'audio/audio_power_monitor.h', 'audio/audio_source_diverter.h', 'audio/audio_util.cc', 'audio/audio_util.h', @@ -885,7 +885,7 @@ 'audio/audio_output_device_unittest.cc', 'audio/audio_output_proxy_unittest.cc', 'audio/audio_parameters_unittest.cc', - 'audio/audio_silence_detector_unittest.cc', + 'audio/audio_power_monitor_unittest.cc', 'audio/cross_process_notification_unittest.cc', 'audio/fake_audio_consumer_unittest.cc', 'audio/ios/audio_manager_ios_unittest.cc', |