Commit a49a7aff authored by miu@chromium.org's avatar miu@chromium.org

Crash fix: Remove MessageLoop from AudioPowerMonitor and instead use...

Crash fix: Remove MessageLoop from AudioPowerMonitor and instead use MessageLoopProxy in AudioOutputController.

Root cause: AudioPowerMonitor held a reference to the audio thread's MessageLoop and erroneously assumed it would be valid until after the audio stream is closed.  However, at browser shutdown, it's possible for audio streams to be closed by the correct thread, but *after* the MessageLoop associated with the thread is destroyed.

BUG=268629
TEST=media_unittests and manual confirmation by running a browser with the --enable-audible-notifications command-line flag.

Review URL: https://chromiumcodereview.appspot.com/22339024

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@217142 0039d316-1c4b-4281-b951-d872f2087c98
parent f4860129
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "media/audio/audio_power_monitor.h"
#include "media/audio/audio_util.h" #include "media/audio/audio_util.h"
#include "media/audio/shared_memory_util.h" #include "media/audio/shared_memory_util.h"
#include "media/base/scoped_histogram_timer.h" #include "media/base/scoped_histogram_timer.h"
...@@ -49,7 +48,10 @@ AudioOutputController::AudioOutputController(AudioManager* audio_manager, ...@@ -49,7 +48,10 @@ AudioOutputController::AudioOutputController(AudioManager* audio_manager,
num_allowed_io_(0), num_allowed_io_(0),
sync_reader_(sync_reader), sync_reader_(sync_reader),
message_loop_(audio_manager->GetMessageLoop()), message_loop_(audio_manager->GetMessageLoop()),
number_polling_attempts_left_(0) { number_polling_attempts_left_(0),
power_monitor_(
params.sample_rate(),
TimeDelta::FromMilliseconds(kPowerMeasurementTimeConstantMillis)) {
DCHECK(audio_manager); DCHECK(audio_manager);
DCHECK(handler_); DCHECK(handler_);
DCHECK(sync_reader_); DCHECK(sync_reader_);
...@@ -156,17 +158,13 @@ void AudioOutputController::DoPlay() { ...@@ -156,17 +158,13 @@ void AudioOutputController::DoPlay() {
state_ = kPlaying; state_ = kPlaying;
// Start monitoring power levels and send an initial notification that we're power_monitor_.Reset();
// starting in silence. power_poll_callback_.Reset(
handler_->OnPowerMeasured(AudioPowerMonitor::zero_power(), false); base::Bind(&AudioOutputController::ReportPowerMeasurementPeriodically,
power_monitor_callback_.Reset( this));
base::Bind(&EventHandler::OnPowerMeasured, base::Unretained(handler_))); // Run the callback to send an initial notification that we're starting in
power_monitor_.reset(new AudioPowerMonitor( // silence, and to schedule periodic callbacks.
params_.sample_rate(), power_poll_callback_.callback().Run();
TimeDelta::FromMilliseconds(kPowerMeasurementTimeConstantMillis),
TimeDelta::FromSeconds(1) / kPowerMeasurementsPerSecond,
base::MessageLoop::current(),
power_monitor_callback_.callback()));
// We start the AudioOutputStream lazily. // We start the AudioOutputStream lazily.
AllowEntryToOnMoreIOData(); AllowEntryToOnMoreIOData();
...@@ -175,6 +173,16 @@ void AudioOutputController::DoPlay() { ...@@ -175,6 +173,16 @@ void AudioOutputController::DoPlay() {
handler_->OnPlaying(); handler_->OnPlaying();
} }
void AudioOutputController::ReportPowerMeasurementPeriodically() {
DCHECK(message_loop_->BelongsToCurrentThread());
const std::pair<float, bool>& reading =
power_monitor_.ReadCurrentPowerAndClip();
handler_->OnPowerMeasured(reading.first, reading.second);
message_loop_->PostDelayedTask(
FROM_HERE, power_poll_callback_.callback(),
TimeDelta::FromSeconds(1) / kPowerMeasurementsPerSecond);
}
void AudioOutputController::StopStream() { void AudioOutputController::StopStream() {
DCHECK(message_loop_->BelongsToCurrentThread()); DCHECK(message_loop_->BelongsToCurrentThread());
...@@ -182,11 +190,7 @@ void AudioOutputController::StopStream() { ...@@ -182,11 +190,7 @@ void AudioOutputController::StopStream() {
stream_->Stop(); stream_->Stop();
DisallowEntryToOnMoreIOData(); DisallowEntryToOnMoreIOData();
// Stop monitoring power levels. By canceling power_monitor_callback_, any power_poll_callback_.Cancel();
// 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; state_ = kPaused;
} }
...@@ -279,7 +283,7 @@ int AudioOutputController::OnMoreIOData(AudioBus* source, ...@@ -279,7 +283,7 @@ int AudioOutputController::OnMoreIOData(AudioBus* source,
sync_reader_->UpdatePendingBytes( sync_reader_->UpdatePendingBytes(
buffers_state.total_bytes() + frames * params_.GetBytesPerFrame()); buffers_state.total_bytes() + frames * params_.GetBytesPerFrame());
power_monitor_->Scan(*dest, frames); power_monitor_.Scan(*dest, frames);
AllowEntryToOnMoreIOData(); AllowEntryToOnMoreIOData();
return frames; return frames;
......
...@@ -9,10 +9,9 @@ ...@@ -9,10 +9,9 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/cancelable_callback.h" #include "base/cancelable_callback.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/timer/timer.h"
#include "media/audio/audio_io.h" #include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h" #include "media/audio/audio_manager.h"
#include "media/audio/audio_power_monitor.h"
#include "media/audio/audio_source_diverter.h" #include "media/audio/audio_source_diverter.h"
#include "media/audio/simple_sources.h" #include "media/audio/simple_sources.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
...@@ -53,8 +52,6 @@ ...@@ -53,8 +52,6 @@
namespace media { namespace media {
class AudioPowerMonitor;
class MEDIA_EXPORT AudioOutputController class MEDIA_EXPORT AudioOutputController
: public base::RefCountedThreadSafe<AudioOutputController>, : public base::RefCountedThreadSafe<AudioOutputController>,
public AudioOutputStream::AudioSourceCallback, public AudioOutputStream::AudioSourceCallback,
...@@ -182,9 +179,9 @@ class MEDIA_EXPORT AudioOutputController ...@@ -182,9 +179,9 @@ class MEDIA_EXPORT AudioOutputController
void DoStartDiverting(AudioOutputStream* to_stream); void DoStartDiverting(AudioOutputStream* to_stream);
void DoStopDiverting(); void DoStopDiverting();
// Called at regular intervals during playback to check for a change in // Calls EventHandler::OnPowerMeasured() with the current power level and then
// silence and call EventHandler::OnAudible() when state changes occur. // schedules itself to be called again later.
void MaybeInvokeAudibleCallback(); void ReportPowerMeasurementPeriodically();
// Helper method that stops the physical stream. // Helper method that stops the physical stream.
void StopStream(); void StopStream();
...@@ -234,11 +231,11 @@ class MEDIA_EXPORT AudioOutputController ...@@ -234,11 +231,11 @@ class MEDIA_EXPORT AudioOutputController
// Number of times left. // Number of times left.
int number_polling_attempts_left_; int number_polling_attempts_left_;
// Scans audio samples from OnMoreIOData() as input and causes // Scans audio samples from OnMoreIOData() as input to compute power levels.
// EventHandler::OnPowerMeasured() to be called with power level measurements AudioPowerMonitor power_monitor_;
// at regular intervals.
scoped_ptr<AudioPowerMonitor> power_monitor_; // Periodic callback to report power levels during playback.
base::CancelableCallback<void(float, bool)> power_monitor_callback_; base::CancelableClosure power_poll_callback_;
DISALLOW_COPY_AND_ASSIGN(AudioOutputController); DISALLOW_COPY_AND_ASSIGN(AudioOutputController);
}; };
......
...@@ -7,38 +7,28 @@ ...@@ -7,38 +7,28 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include "base/bind.h"
#include "base/float_util.h" #include "base/float_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "media/base/audio_bus.h" #include "media/base/audio_bus.h"
namespace media { namespace media {
AudioPowerMonitor::AudioPowerMonitor( AudioPowerMonitor::AudioPowerMonitor(
int sample_rate, int sample_rate, const base::TimeDelta& time_constant)
const base::TimeDelta& time_constant,
const base::TimeDelta& measurement_period,
base::MessageLoop* message_loop,
const PowerMeasurementCallback& callback)
: sample_weight_( : sample_weight_(
1.0f - expf(-1.0f / (sample_rate * time_constant.InSecondsF()))), 1.0f - expf(-1.0f / (sample_rate * time_constant.InSecondsF()))) {
num_frames_per_callback_(sample_rate * measurement_period.InSecondsF()), Reset();
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() { AudioPowerMonitor::~AudioPowerMonitor() {
} }
void AudioPowerMonitor::Reset() {
power_reading_ = average_power_ = 0.0f;
clipped_reading_ = has_clipped_ = false;
}
void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) { void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) {
DCHECK_LE(num_frames, buffer.frames()); DCHECK_LE(num_frames, buffer.frames());
const int num_channels = buffer.channels(); const int num_channels = buffer.channels();
...@@ -50,10 +40,10 @@ void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) { ...@@ -50,10 +40,10 @@ void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) {
// //
// TODO(miu): Implement optimized SSE/NEON to more efficiently compute the // TODO(miu): Implement optimized SSE/NEON to more efficiently compute the
// results (in media/base/vector_math) in soon-upcoming change. // results (in media/base/vector_math) in soon-upcoming change.
bool clipped = false;
float sum_power = 0.0f; float sum_power = 0.0f;
for (int i = 0; i < num_channels; ++i) { for (int i = 0; i < num_channels; ++i) {
float average_power_this_channel = average_power_; float average_power_this_channel = average_power_;
bool clipped = false;
const float* p = buffer.channel(i); const float* p = buffer.channel(i);
const float* const end_of_samples = p + num_frames; const float* const end_of_samples = p + num_frames;
for (; p < end_of_samples; ++p) { for (; p < end_of_samples; ++p) {
...@@ -64,45 +54,41 @@ void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) { ...@@ -64,45 +54,41 @@ void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) {
(sample_squared - average_power_this_channel) * sample_weight_; (sample_squared - average_power_this_channel) * sample_weight_;
} }
// If data in audio buffer is garbage, ignore its effect on the result. // If data in audio buffer is garbage, ignore its effect on the result.
if (base::IsNaN(average_power_this_channel)) if (base::IsNaN(average_power_this_channel)) {
average_power_this_channel = average_power_; average_power_this_channel = average_power_;
clipped = false;
}
sum_power += average_power_this_channel; sum_power += average_power_this_channel;
has_clipped_ |= clipped;
} }
// Update accumulated results. // Update accumulated results, with clamping for sanity.
average_power_ = std::max(0.0f, std::min(1.0f, sum_power / num_channels)); 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; // Push results for reading by other threads, non-blocking.
if (reading_lock_.Try()) {
// Once enough frames have been scanned, report the accumulated results. power_reading_ = average_power_;
if (frames_since_last_notification_ >= num_frames_per_callback_) { if (has_clipped_) {
// Note: Forgo making redundant callbacks when results remain unchanged. clipped_reading_ = true;
// Part of this is to pin-down the power to zero if it is insignificantly has_clipped_ = false;
// 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 =
power > 0.0f ? 10.0f * log10f(power) : zero_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; reading_lock_.Release();
frames_since_last_notification_ = 0;
} }
} }
std::pair<float, bool> AudioPowerMonitor::ReadCurrentPowerAndClip() {
base::AutoLock for_reading(reading_lock_);
// Convert power level to dBFS units, and pin it down to zero if it is
// insignificantly small.
const float kInsignificantPower = 1.0e-10f; // -100 dBFS
const float power_dbfs = power_reading_ < kInsignificantPower ? zero_power() :
10.0f * log10f(power_reading_);
const bool clipped = clipped_reading_;
clipped_reading_ = false;
return std::make_pair(power_dbfs, clipped);
}
} // namespace media } // namespace media
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
#define MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_ #define MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_
#include <limits> #include <limits>
#include <utility>
#include "base/callback.h" #include "base/callback.h"
#include "base/synchronization/lock.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
// An audio signal power monitor. It is periodically provided an AudioBus by // An audio signal power monitor. It is periodically provided an AudioBus by
...@@ -24,7 +26,6 @@ ...@@ -24,7 +26,6 @@
// undetermined/unbounded amount of run-time. // undetermined/unbounded amount of run-time.
namespace base { namespace base {
class MessageLoop;
class TimeDelta; class TimeDelta;
} }
...@@ -34,31 +35,28 @@ class AudioBus; ...@@ -34,31 +35,28 @@ class AudioBus;
class MEDIA_EXPORT AudioPowerMonitor { class MEDIA_EXPORT AudioPowerMonitor {
public: 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| // |sample_rate| is the audio signal sample rate (Hz). |time_constant|
// characterizes how samples are averaged over time to determine the power // 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 // 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 // ~63.2% of maximum given a step input signal.
// time length of signal to analyze before invoking the callback to report the AudioPowerMonitor(int sample_rate, const base::TimeDelta& time_constant);
// 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(); ~AudioPowerMonitor();
// Reset power monitor to initial state (zero power level). This should not
// be called while another thread is scanning.
void Reset();
// Scan more |frames| of audio data from |buffer|. It is safe to call this // Scan more |frames| of audio data from |buffer|. It is safe to call this
// from a real-time priority thread. // from a real-time priority thread.
void Scan(const AudioBus& buffer, int frames); void Scan(const AudioBus& buffer, int frames);
// Returns the current power level in dBFS and clip status. Clip status is
// true whenever any *one* sample scanned exceeded maximum amplitude since
// this method's last invocation. It is safe to call this method from any
// thread.
std::pair<float, bool> ReadCurrentPowerAndClip();
// dBFS value corresponding to zero power in the audio signal. // dBFS value corresponding to zero power in the audio signal.
static float zero_power() { return -std::numeric_limits<float>::infinity(); } static float zero_power() { return -std::numeric_limits<float>::infinity(); }
...@@ -70,23 +68,16 @@ class MEDIA_EXPORT AudioPowerMonitor { ...@@ -70,23 +68,16 @@ class MEDIA_EXPORT AudioPowerMonitor {
// |sample_rate| and |time_constant|. // |sample_rate| and |time_constant|.
const float sample_weight_; const float sample_weight_;
// Number of audio frames to be scanned before reporting the current power // Accumulated results over one or more calls to Scan(). These should only be
// level via callback, as computed from |sample_rate| and // touched by the thread invoking Scan().
// |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_; float average_power_;
bool clipped_since_last_notification_; bool has_clipped_;
int frames_since_last_notification_;
// Keep track of last reported results to forgo making redundant callbacks. // Copies of power and clip status, used to deliver results synchronously
float last_reported_power_; // across threads.
bool last_reported_clipped_; base::Lock reading_lock_;
float power_reading_;
bool clipped_reading_;
DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitor); DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitor);
}; };
......
...@@ -6,9 +6,6 @@ ...@@ -6,9 +6,6 @@
#include <limits> #include <limits>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "media/base/audio_bus.h" #include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -19,7 +16,6 @@ static const int kSampleRate = 48000; ...@@ -19,7 +16,6 @@ static const int kSampleRate = 48000;
static const int kFramesPerBuffer = 128; static const int kFramesPerBuffer = 128;
static const int kTimeConstantMillis = 5; static const int kTimeConstantMillis = 5;
static const int kMeasurementPeriodMillis = 20;
namespace { namespace {
...@@ -158,26 +154,22 @@ class MeasurementObserver { ...@@ -158,26 +154,22 @@ class MeasurementObserver {
class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> { class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> {
public: public:
AudioPowerMonitorTest() AudioPowerMonitorTest()
: power_monitor_( : power_monitor_(kSampleRate,
kSampleRate, base::TimeDelta::FromMilliseconds(kTimeConstantMillis)) {
base::TimeDelta::FromMilliseconds(kTimeConstantMillis), }
base::TimeDelta::FromMilliseconds(kMeasurementPeriodMillis),
&message_loop_,
base::Bind(&AudioPowerMonitorTest::OnPowerMeasured,
base::Unretained(this))) {}
void FeedAndCheckExpectedPowerIsMeasured( void FeedAndCheckExpectedPowerIsMeasured(
const AudioBus& bus, float power, bool clipped) { const AudioBus& bus, float power, bool clipped) {
// Feed the AudioPowerMonitor. It should post tasks to |message_loop_|. // Feed the AudioPowerMonitor, read measurements from it, and record them in
// MeasurementObserver.
static const int kNumFeedIters = 100; 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); MeasurementObserver observer(power, clipped);
current_observer_ = &observer; for (int i = 0; i < kNumFeedIters; ++i) {
message_loop_.RunUntilIdle(); power_monitor_.Scan(bus, bus.frames());
current_observer_ = NULL; const std::pair<float, bool>& reading =
power_monitor_.ReadCurrentPowerAndClip();
observer.OnPowerMeasured(reading.first, reading.second);
}
// Check that the results recorded by the observer are the same whole-number // Check that the results recorded by the observer are the same whole-number
// dBFS. // dBFS.
...@@ -187,14 +179,7 @@ class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> { ...@@ -187,14 +179,7 @@ class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> {
} }
private: private:
void OnPowerMeasured(float power, bool clipped) {
CHECK(current_observer_);
current_observer_->OnPowerMeasured(power, clipped);
}
base::MessageLoop message_loop_;
AudioPowerMonitor power_monitor_; AudioPowerMonitor power_monitor_;
MeasurementObserver* current_observer_;
DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest); DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest);
}; };
...@@ -303,7 +288,7 @@ INSTANTIATE_TEST_CASE_P( ...@@ -303,7 +288,7 @@ INSTANTIATE_TEST_CASE_P(
TestScenario(kMonoMaxAmplitudeWithClip2, 1, 4, TestScenario(kMonoMaxAmplitudeWithClip2, 1, 4,
AudioPowerMonitor::max_power(), true), AudioPowerMonitor::max_power(), true),
TestScenario(kMonoSilentNoise, 1, 2, TestScenario(kMonoSilentNoise, 1, 2,
AudioPowerMonitor::zero_power(), true). AudioPowerMonitor::zero_power(), false).
WithABadSample(std::numeric_limits<float>::infinity()), WithABadSample(std::numeric_limits<float>::infinity()),
TestScenario(kMonoHalfMaxAmplitude, 1, 4, TestScenario(kMonoHalfMaxAmplitude, 1, 4,
AudioPowerMonitor::zero_power(), false). AudioPowerMonitor::zero_power(), false).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment