summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authormiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-19 20:55:56 +0000
committermiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-19 20:55:56 +0000
commite2cdbb87ea16864fec2814f6562d3ec64fa4a9a4 (patch)
treed395b3169477c9cd375272741e29f07cf1ad3fd9 /media/audio
parentb8145ed316d8626baa7d4a9fd83dd4e39868bc67 (diff)
downloadchromium_src-e2cdbb87ea16864fec2814f6562d3ec64fa4a9a4.zip
chromium_src-e2cdbb87ea16864fec2814f6562d3ec64fa4a9a4.tar.gz
chromium_src-e2cdbb87ea16864fec2814f6562d3ec64fa4a9a4.tar.bz2
Silence detection for audio output UI favicon indicator.
Added a ProbablyContainsSilence() utility method to media::AudioBus, and then added silence detection logic to media::AudioOutputController. While the detection logic will run on the "native audio thread," it should use an inconsequential amount of CPU time. AudioOutputController will invoke a new EventHandler::OnAudible() callback no more often than every 50 ms, and only when transitioning to/from periods of silence. Also, to avoid O(N) look-ups in AudioRendererHost for each call to any AudioOutputController::EventHandler method, the interface was simplified such that each stream/controller gets its own EventHandler instance. Changed AudioRendererHost::AudioEntry to become the class that implements the EventHandler interface. Lots of related clean-ups throughout audio_renderer_host.cc to deflate the code. BUG=178343 TEST=media_unittests, content_unittests, and confirmation by running chrome manually TBR=avi Review URL: https://chromiumcodereview.appspot.com/12426002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@189095 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r--media/audio/audio_output_controller.cc38
-rw-r--r--media/audio/audio_output_controller.h25
-rw-r--r--media/audio/audio_output_controller_unittest.cc35
-rw-r--r--media/audio/audio_silence_detector.cc134
-rw-r--r--media/audio/audio_silence_detector.h112
-rw-r--r--media/audio/audio_silence_detector_unittest.cc227
6 files changed, 542 insertions, 29 deletions
diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc
index 9120706..d162cc2 100644
--- a/media/audio/audio_output_controller.cc
+++ b/media/audio/audio_output_controller.cc
@@ -10,6 +10,7 @@
#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "build/build_config.h"
+#include "media/audio/audio_silence_detector.h"
#include "media/audio/audio_util.h"
#include "media/audio/shared_memory_util.h"
@@ -18,6 +19,17 @@ 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;
+
+// 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.
+
// Polling-related constants.
const int AudioOutputController::kPollNumAttempts = 3;
const int AudioOutputController::kPollPauseInMilliseconds = 3;
@@ -109,7 +121,7 @@ void AudioOutputController::DoCreate(bool is_for_device_change) {
state_ = kError;
// TODO(hclam): Define error types.
- handler_->OnError(this, 0);
+ handler_->OnError(0);
return;
}
@@ -118,7 +130,7 @@ void AudioOutputController::DoCreate(bool is_for_device_change) {
state_ = kError;
// TODO(hclam): Define error types.
- handler_->OnError(this, 0);
+ handler_->OnError(0);
return;
}
@@ -135,7 +147,7 @@ void AudioOutputController::DoCreate(bool is_for_device_change) {
// And then report we have been created if we haven't done so already.
if (!is_for_device_change)
- handler_->OnCreated(this);
+ handler_->OnCreated();
}
void AudioOutputController::DoPlay() {
@@ -185,12 +197,20 @@ void AudioOutputController::StartStream() {
DCHECK(message_loop_->BelongsToCurrentThread());
state_ = kPlaying;
+ silence_detector_.reset(new AudioSilenceDetector(
+ params_.sample_rate(),
+ TimeDelta::FromMilliseconds(kQuestionableSilencePeriodMillis),
+ kIndistinguishableSilenceThreshold));
+
// We start the AudioOutputStream lazily.
AllowEntryToOnMoreIOData();
stream_->Start(this);
- // Tell the event handler that we are now playing.
- handler_->OnPlaying(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() {
@@ -203,6 +223,8 @@ void AudioOutputController::StopStream() {
} else if (state_ == kPlaying) {
stream_->Stop();
DisallowEntryToOnMoreIOData();
+ silence_detector_->Stop(true);
+ silence_detector_.reset();
state_ = kPaused;
}
}
@@ -218,7 +240,7 @@ void AudioOutputController::DoPause() {
// Send a special pause mark to the low-latency audio thread.
sync_reader_->UpdatePendingBytes(kPauseMark);
- handler_->OnPaused(this);
+ handler_->OnPaused();
}
void AudioOutputController::DoFlush() {
@@ -259,7 +281,7 @@ void AudioOutputController::DoSetVolume(double volume) {
void AudioOutputController::DoReportError(int code) {
DCHECK(message_loop_->BelongsToCurrentThread());
if (state_ != kClosed)
- handler_->OnError(this, code);
+ handler_->OnError(code);
}
int AudioOutputController::OnMoreData(AudioBus* dest,
@@ -288,6 +310,8 @@ int AudioOutputController::OnMoreIOData(AudioBus* source,
sync_reader_->UpdatePendingBytes(
buffers_state.total_bytes() + frames * params_.GetBytesPerFrame());
+ silence_detector_->Scan(dest, frames);
+
AllowEntryToOnMoreIOData();
return frames;
}
diff --git a/media/audio/audio_output_controller.h b/media/audio/audio_output_controller.h
index e8fae22..cfb685f 100644
--- a/media/audio/audio_output_controller.h
+++ b/media/audio/audio_output_controller.h
@@ -8,7 +8,9 @@
#include "base/atomic_ref_count.h"
#include "base/callback.h"
#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
+#include "base/timer.h"
#include "media/audio/audio_buffers_state.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
@@ -57,6 +59,8 @@ class MessageLoop;
namespace media {
+class AudioSilenceDetector;
+
class MEDIA_EXPORT AudioOutputController
: public base::RefCountedThreadSafe<AudioOutputController>,
public AudioOutputStream::AudioSourceCallback,
@@ -67,12 +71,12 @@ class MEDIA_EXPORT AudioOutputController
// following methods are called on the audio manager thread.
class MEDIA_EXPORT EventHandler {
public:
- virtual void OnCreated(AudioOutputController* controller) = 0;
- virtual void OnPlaying(AudioOutputController* controller) = 0;
- virtual void OnPaused(AudioOutputController* controller) = 0;
- virtual void OnError(AudioOutputController* controller, int error_code) = 0;
- virtual void OnDeviceChange(AudioOutputController* controller,
- int new_buffer_size, int new_sample_rate) = 0;
+ virtual void OnCreated() = 0;
+ virtual void OnPlaying() = 0;
+ virtual void OnAudible(bool is_audible) = 0;
+ virtual void OnPaused() = 0;
+ virtual void OnError(int error_code) = 0;
+ virtual void OnDeviceChange(int new_buffer_size, int new_sample_rate) = 0;
protected:
virtual ~EventHandler() {}
@@ -193,6 +197,10 @@ class MEDIA_EXPORT AudioOutputController
void DoStartDiverting(AudioOutputStream* to_stream);
void DoStopDiverting();
+ // Called at regular intervals during playback to check for a change in
+ // silence and call EventHandler::OnAudible() when state changes occur.
+ void MaybeInvokeAudibleCallback();
+
// Helper methods that start/stop physical stream.
void StartStream();
void StopStream();
@@ -245,6 +253,11 @@ class MEDIA_EXPORT AudioOutputController
// (when starting-up a stream).
base::WeakPtrFactory<AudioOutputController> weak_this_;
+ // 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_;
+
DISALLOW_COPY_AND_ASSIGN(AudioOutputController);
};
diff --git a/media/audio/audio_output_controller_unittest.cc b/media/audio/audio_output_controller_unittest.cc
index 944d661..699810f 100644
--- a/media/audio/audio_output_controller_unittest.cc
+++ b/media/audio/audio_output_controller_unittest.cc
@@ -38,13 +38,13 @@ class MockAudioOutputControllerEventHandler
public:
MockAudioOutputControllerEventHandler() {}
- MOCK_METHOD1(OnCreated, void(AudioOutputController* controller));
- MOCK_METHOD1(OnPlaying, void(AudioOutputController* controller));
- MOCK_METHOD1(OnPaused, void(AudioOutputController* controller));
- MOCK_METHOD2(OnError, void(AudioOutputController* controller,
- int error_code));
- MOCK_METHOD3(OnDeviceChange, void(AudioOutputController* controller,
- int new_buffer_size, int new_sample_rate));
+ MOCK_METHOD0(OnCreated, void());
+ MOCK_METHOD0(OnPlaying, void());
+ MOCK_METHOD1(OnAudible, void(bool is_audible));
+ MOCK_METHOD0(OnPaused, void());
+ MOCK_METHOD1(OnError, void(int error_code));
+ MOCK_METHOD2(OnDeviceChange, void(int new_buffer_size, int new_sample_rate));
+
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerEventHandler);
};
@@ -117,7 +117,7 @@ class AudioOutputControllerTest : public testing::Test {
kSampleRate, kBitsPerSample, samples_per_packet);
if (params_.IsValid()) {
- EXPECT_CALL(mock_event_handler_, OnCreated(NotNull()))
+ EXPECT_CALL(mock_event_handler_, OnCreated())
.WillOnce(SignalEvent(&create_event_));
}
@@ -131,14 +131,17 @@ class AudioOutputControllerTest : public testing::Test {
}
void Play() {
- // Expect the event handler to receive one OnPlaying() call.
- EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull()))
+ // Expect the event handler to receive one OnPlaying() call and one or more
+ // OnAudible() calls.
+ EXPECT_CALL(mock_event_handler_, OnPlaying())
.WillOnce(SignalEvent(&play_event_));
+ EXPECT_CALL(mock_event_handler_, OnAudible(_))
+ .Times(AtLeast(1));
// During playback, the mock pretends to provide audio data rendered and
// sent from the render process.
EXPECT_CALL(mock_sync_reader_, UpdatePendingBytes(_))
- .Times(AtLeast(2));
+ .Times(AtLeast(1));
EXPECT_CALL(mock_sync_reader_, Read(_, _))
.WillRepeatedly(DoAll(PopulateBuffer(),
SignalEvent(&read_event_),
@@ -151,7 +154,7 @@ class AudioOutputControllerTest : public testing::Test {
void Pause() {
// Expect the event handler to receive one OnPaused() call.
- EXPECT_CALL(mock_event_handler_, OnPaused(NotNull()))
+ EXPECT_CALL(mock_event_handler_, OnPaused())
.WillOnce(SignalEvent(&pause_event_));
controller_->Pause();
@@ -160,9 +163,9 @@ class AudioOutputControllerTest : public testing::Test {
void ChangeDevice() {
// Expect the event handler to receive one OnPaying() call and no OnPaused()
// call.
- EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull()))
+ EXPECT_CALL(mock_event_handler_, OnPlaying())
.WillOnce(SignalEvent(&play_event_));
- EXPECT_CALL(mock_event_handler_, OnPaused(NotNull()))
+ EXPECT_CALL(mock_event_handler_, OnPaused())
.Times(0);
// Simulate a device change event to AudioOutputController from the
@@ -176,7 +179,7 @@ class AudioOutputControllerTest : public testing::Test {
if (was_playing) {
// Expect the handler to receive one OnPlaying() call as a result of the
// stream switching.
- EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull()))
+ EXPECT_CALL(mock_event_handler_, OnPlaying())
.WillOnce(SignalEvent(&play_event_));
}
@@ -208,7 +211,7 @@ class AudioOutputControllerTest : public testing::Test {
if (was_playing) {
// Expect the handler to receive one OnPlaying() call as a result of the
// stream switching back.
- EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull()))
+ EXPECT_CALL(mock_event_handler_, OnPlaying())
.WillOnce(SignalEvent(&play_event_));
}
diff --git a/media/audio/audio_silence_detector.cc b/media/audio/audio_silence_detector.cc
new file mode 100644
index 0000000..4f4bc06
--- /dev/null
+++ b/media/audio/audio_silence_detector.cc
@@ -0,0 +1,134 @@
+// 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.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
new file mode 100644
index 0000000..b6c52a7
--- /dev/null
+++ b/media/audio/audio_silence_detector.h
@@ -0,0 +1,112 @@
+// 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.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
new file mode 100644
index 0000000..52e6958
--- /dev/null
+++ b/media/audio/audio_silence_detector_unittest.cc
@@ -0,0 +1,227 @@
+// 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.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/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