Commit 9e0a7c10 authored by phoglund's avatar phoglund Committed by Commit bot

Moved the fake input stream's processing onto the audio worker thread.

The reason for this is that the fake input stream would not work on Mac
where the audio non-worker code runs on the UI thread. Therefore, the
fake input stream would get starved for instance in the WebRTC audio
quality tests and not play any audio (those tests block the UI thread
while recording).

This patch introduces a worker precisely like the fake audio consumer
used by the fake output stream.

BUG=453907,446859

Review URL: https://codereview.chromium.org/922663002

Cr-Commit-Position: refs/heads/master@{#317557}
parent 2c8ef923
......@@ -12,7 +12,7 @@
#include "content/public/browser/web_contents.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/audio/fake_audio_consumer.h"
#include "media/audio/fake_audio_worker.h"
#include "media/base/bind_to_current_loop.h"
namespace content {
......@@ -30,14 +30,17 @@ namespace {
class AudioDiscarder : public media::AudioOutputStream {
public:
explicit AudioDiscarder(const media::AudioParameters& params)
: consumer_(media::AudioManager::Get()->GetWorkerTaskRunner(), params) {}
: worker_(media::AudioManager::Get()->GetWorkerTaskRunner(), params),
audio_bus_(media::AudioBus::Create(params)) {}
// AudioOutputStream implementation.
bool Open() override { return true; }
void Start(AudioSourceCallback* callback) override {
consumer_.Start(base::Bind(&AudioDiscarder::FetchAudioData, callback));
worker_.Start(base::Bind(&AudioDiscarder::FetchAudioData,
base::Unretained(this),
callback));
}
void Stop() override { consumer_.Stop(); }
void Stop() override { worker_.Stop(); }
void SetVolume(double volume) override {}
void GetVolume(double* volume) override { *volume = 0; }
void Close() override { delete this; }
......@@ -45,13 +48,13 @@ class AudioDiscarder : public media::AudioOutputStream {
private:
~AudioDiscarder() override {}
static void FetchAudioData(AudioSourceCallback* callback,
media::AudioBus* audio_bus) {
callback->OnMoreData(audio_bus, 0);
void FetchAudioData(AudioSourceCallback* callback) {
callback->OnMoreData(audio_bus_.get(), 0);
}
// Calls FetchAudioData() at regular intervals and discards the data.
media::FakeAudioConsumer consumer_;
media::FakeAudioWorker worker_;
scoped_ptr<media::AudioBus> audio_bus_;
DISALLOW_COPY_AND_ASSIGN(AudioDiscarder);
};
......
......@@ -84,8 +84,6 @@ source_set("audio") {
"audio_source_diverter.h",
"clockless_audio_sink.cc",
"clockless_audio_sink.h",
"fake_audio_consumer.cc",
"fake_audio_consumer.h",
"fake_audio_input_stream.cc",
"fake_audio_input_stream.h",
"fake_audio_log_factory.cc",
......@@ -94,6 +92,8 @@ source_set("audio") {
"fake_audio_manager.h",
"fake_audio_output_stream.cc",
"fake_audio_output_stream.h",
"fake_audio_worker.cc",
"fake_audio_worker.h",
"null_audio_sink.cc",
"null_audio_sink.h",
"sample_rates.cc",
......@@ -276,7 +276,7 @@ source_set("unittests") {
"audio_output_proxy_unittest.cc",
"audio_parameters_unittest.cc",
"audio_power_monitor_unittest.cc",
"fake_audio_consumer_unittest.cc",
"fake_audio_worker_unittest.cc",
"simple_sources_unittest.cc",
"virtual_audio_input_stream_unittest.cc",
"virtual_audio_output_stream_unittest.cc",
......
......@@ -56,6 +56,8 @@ class MEDIA_EXPORT AudioOutputStream {
// itself such as creating Windows or initializing COM.
class MEDIA_EXPORT AudioSourceCallback {
public:
virtual ~AudioSourceCallback() {}
// Provide more data by fully filling |dest|. The source will return
// the number of frames it filled. |total_bytes_delay| contains current
// number of bytes of delay buffered by the AudioOutputStream.
......@@ -66,9 +68,6 @@ class MEDIA_EXPORT AudioOutputStream {
// a good place to stop accumulating sound data since is is likely that
// playback will not continue.
virtual void OnError(AudioOutputStream* stream) = 0;
protected:
virtual ~AudioSourceCallback() {}
};
virtual ~AudioOutputStream() {}
......
This diff is collapsed.
......@@ -9,24 +9,25 @@
#include <vector>
#include "base/files/file_path.h"
#include "base/callback_forward.h"
#include "base/memory/scoped_ptr.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/sounds/wav_audio_handler.h"
#include "media/base/audio_converter.h"
#include "media/audio/fake_audio_worker.h"
namespace media {
class AudioBus;
class AudioManagerBase;
class SimpleSource;
// This class can either generate a beep sound or play audio from a file.
// This class acts as a fake audio input stream. The default is to generate a
// beeping sound unless --use-file-for-fake-audio-capture=<file> is specified,
// in which case the indicated .wav file will be read and played into the
// stream.
class MEDIA_EXPORT FakeAudioInputStream
: public AudioInputStream, AudioConverter::InputCallback {
: public AudioInputStream {
public:
static AudioInputStream* MakeFakeStream(
AudioManagerBase* manager, const AudioParameters& params);
......@@ -42,12 +43,13 @@ class MEDIA_EXPORT FakeAudioInputStream
bool SetAutomaticGainControl(bool enabled) override;
bool GetAutomaticGainControl() override;
// Generate one beep sound. This method is called by
// FakeVideoCaptureDevice to test audio/video synchronization.
// This is a static method because FakeVideoCaptureDevice is
// disconnected from an audio device. This means only one instance of
// this class gets to respond, which is okay because we assume there's
// only one stream for this testing purpose.
// Generate one beep sound. This method is called by FakeVideoCaptureDevice to
// test audio/video synchronization. This is a static method because
// FakeVideoCaptureDevice is disconnected from an audio device. This means
// only one instance of this class gets to respond, which is okay because we
// assume there's only one stream for this testing purpose. Furthermore this
// method will do nothing if --use-file-for-fake-audio-capture is specified
// since the input stream will be playing from a file instead of beeping.
// TODO(hclam): Make this non-static. To do this we'll need to fix
// crbug.com/159053 such that video capture device is aware of audio
// input stream.
......@@ -58,42 +60,16 @@ class MEDIA_EXPORT FakeAudioInputStream
const AudioParameters& params);
~FakeAudioInputStream() override;
void DoCallback();
// Opens this stream reading from a |wav_filename| rather than beeping.
void OpenInFileMode(const base::FilePath& wav_filename);
// Returns true if the device is playing from a file; false if we're beeping.
bool PlayingFromFile();
void PlayFile();
void PlayBeep();
scoped_ptr<AudioOutputStream::AudioSourceCallback> ChooseSource();
void ReadAudioFromSource();
AudioManagerBase* audio_manager_;
AudioInputCallback* callback_;
scoped_ptr<uint8[]> buffer_;
int buffer_size_;
FakeAudioWorker fake_audio_worker_;
AudioParameters params_;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
base::TimeTicks last_callback_time_;
base::TimeDelta callback_interval_;
base::TimeDelta interval_from_last_beep_;
int beep_duration_in_buffers_;
int beep_generated_in_buffers_;
int beep_period_in_frames_;
scoped_ptr<AudioOutputStream::AudioSourceCallback> audio_source_;
scoped_ptr<media::AudioBus> audio_bus_;
scoped_ptr<uint8[]> wav_file_data_;
scoped_ptr<media::WavAudioHandler> wav_audio_handler_;
scoped_ptr<media::AudioConverter> file_audio_converter_;
int wav_file_read_pos_;
// Allows us to run tasks on the FakeAudioInputStream instance which are
// bound by its lifetime.
base::WeakPtrFactory<FakeAudioInputStream> weak_factory_;
// If running in file mode, this provides audio data from wav_audio_handler_.
double ProvideInput(AudioBus* audio_bus,
base::TimeDelta buffer_delay) override;
DISALLOW_COPY_AND_ASSIGN(FakeAudioInputStream);
};
......
......@@ -22,7 +22,8 @@ FakeAudioOutputStream::FakeAudioOutputStream(AudioManagerBase* manager,
const AudioParameters& params)
: audio_manager_(manager),
callback_(NULL),
fake_consumer_(manager->GetWorkerTaskRunner(), params) {
fake_worker_(manager->GetWorkerTaskRunner(), params),
audio_bus_(AudioBus::Create(params)) {
}
FakeAudioOutputStream::~FakeAudioOutputStream() {
......@@ -31,19 +32,20 @@ FakeAudioOutputStream::~FakeAudioOutputStream() {
bool FakeAudioOutputStream::Open() {
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
audio_bus_->Zero();
return true;
}
void FakeAudioOutputStream::Start(AudioSourceCallback* callback) {
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
callback_ = callback;
fake_consumer_.Start(base::Bind(
fake_worker_.Start(base::Bind(
&FakeAudioOutputStream::CallOnMoreData, base::Unretained(this)));
}
void FakeAudioOutputStream::Stop() {
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
fake_consumer_.Stop();
fake_worker_.Stop();
callback_ = NULL;
}
......@@ -59,9 +61,9 @@ void FakeAudioOutputStream::GetVolume(double* volume) {
*volume = 0;
};
void FakeAudioOutputStream::CallOnMoreData(AudioBus* audio_bus) {
void FakeAudioOutputStream::CallOnMoreData() {
DCHECK(audio_manager_->GetWorkerTaskRunner()->BelongsToCurrentThread());
callback_->OnMoreData(audio_bus, 0);
callback_->OnMoreData(audio_bus_.get(), 0);
}
} // namespace media
......@@ -8,7 +8,7 @@
#include "base/memory/scoped_ptr.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/fake_audio_consumer.h"
#include "media/audio/fake_audio_worker.h"
namespace media {
......@@ -36,11 +36,12 @@ class MEDIA_EXPORT FakeAudioOutputStream : public AudioOutputStream {
~FakeAudioOutputStream() override;
// Task that periodically calls OnMoreData() to consume audio data.
void CallOnMoreData(AudioBus* audio_bus);
void CallOnMoreData();
AudioManagerBase* audio_manager_;
AudioSourceCallback* callback_;
FakeAudioConsumer fake_consumer_;
FakeAudioWorker fake_worker_;
scoped_ptr<AudioBus> audio_bus_;
DISALLOW_COPY_AND_ASSIGN(FakeAudioOutputStream);
};
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/fake_audio_consumer.h"
#include "media/audio/fake_audio_worker.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
......@@ -15,18 +15,17 @@
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "media/audio/audio_parameters.h"
#include "media/base/audio_bus.h"
namespace media {
class FakeAudioConsumer::Worker
: public base::RefCountedThreadSafe<FakeAudioConsumer::Worker> {
class FakeAudioWorker::Worker
: public base::RefCountedThreadSafe<FakeAudioWorker::Worker> {
public:
Worker(const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
const AudioParameters& params);
bool IsStopped();
void Start(const ReadCB& read_cb);
void Start(const base::Closure& worker_cb);
void Stop();
private:
......@@ -39,114 +38,110 @@ class FakeAudioConsumer::Worker
// Cancel any delayed callbacks to DoRead() in the worker loop's queue.
void DoCancel();
// Task that regularly calls |read_cb_| according to the playback rate as
// Task that regularly calls |worker_cb_| according to the playback rate as
// determined by the audio parameters given during construction. Runs on
// the worker loop.
void DoRead();
const scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner_;
const scoped_ptr<AudioBus> audio_bus_;
const base::TimeDelta buffer_duration_;
base::Lock read_cb_lock_; // Held while mutating or running |read_cb_|.
ReadCB read_cb_;
base::Lock worker_cb_lock_; // Held while mutating or running |worker_cb_|.
base::Closure worker_cb_;
base::TimeTicks next_read_time_;
// Used to cancel any delayed tasks still inside the worker loop's queue.
base::CancelableClosure read_task_cb_;
base::CancelableClosure worker_task_cb_;
base::ThreadChecker thread_checker_;
DISALLOW_COPY_AND_ASSIGN(Worker);
};
FakeAudioConsumer::FakeAudioConsumer(
FakeAudioWorker::FakeAudioWorker(
const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
const AudioParameters& params)
: worker_(new Worker(worker_task_runner, params)) {
}
FakeAudioConsumer::~FakeAudioConsumer() {
FakeAudioWorker::~FakeAudioWorker() {
DCHECK(worker_->IsStopped());
}
void FakeAudioConsumer::Start(const ReadCB& read_cb) {
void FakeAudioWorker::Start(const base::Closure& worker_cb) {
DCHECK(worker_->IsStopped());
worker_->Start(read_cb);
worker_->Start(worker_cb);
}
void FakeAudioConsumer::Stop() {
void FakeAudioWorker::Stop() {
worker_->Stop();
}
FakeAudioConsumer::Worker::Worker(
FakeAudioWorker::Worker::Worker(
const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
const AudioParameters& params)
: worker_task_runner_(worker_task_runner),
audio_bus_(AudioBus::Create(params)),
buffer_duration_(base::TimeDelta::FromMicroseconds(
params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond /
static_cast<float>(params.sample_rate()))) {
audio_bus_->Zero();
// Worker can be constructed on any thread, but will DCHECK that its
// Start/Stop methods are called from the same thread.
thread_checker_.DetachFromThread();
}
FakeAudioConsumer::Worker::~Worker() {
DCHECK(read_cb_.is_null());
FakeAudioWorker::Worker::~Worker() {
DCHECK(worker_cb_.is_null());
}
bool FakeAudioConsumer::Worker::IsStopped() {
base::AutoLock scoped_lock(read_cb_lock_);
return read_cb_.is_null();
bool FakeAudioWorker::Worker::IsStopped() {
base::AutoLock scoped_lock(worker_cb_lock_);
return worker_cb_.is_null();
}
void FakeAudioConsumer::Worker::Start(const ReadCB& read_cb) {
void FakeAudioWorker::Worker::Start(const base::Closure& worker_cb) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!read_cb.is_null());
DCHECK(!worker_cb.is_null());
{
base::AutoLock scoped_lock(read_cb_lock_);
DCHECK(read_cb_.is_null());
read_cb_ = read_cb;
base::AutoLock scoped_lock(worker_cb_lock_);
DCHECK(worker_cb_.is_null());
worker_cb_ = worker_cb;
}
worker_task_runner_->PostTask(FROM_HERE, base::Bind(&Worker::DoStart, this));
}
void FakeAudioConsumer::Worker::DoStart() {
void FakeAudioWorker::Worker::DoStart() {
DCHECK(worker_task_runner_->BelongsToCurrentThread());
next_read_time_ = base::TimeTicks::Now();
read_task_cb_.Reset(base::Bind(&Worker::DoRead, this));
read_task_cb_.callback().Run();
worker_task_cb_.Reset(base::Bind(&Worker::DoRead, this));
worker_task_cb_.callback().Run();
}
void FakeAudioConsumer::Worker::Stop() {
void FakeAudioWorker::Worker::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
{
base::AutoLock scoped_lock(read_cb_lock_);
if (read_cb_.is_null())
base::AutoLock scoped_lock(worker_cb_lock_);
if (worker_cb_.is_null())
return;
read_cb_.Reset();
worker_cb_.Reset();
}
worker_task_runner_->PostTask(FROM_HERE, base::Bind(&Worker::DoCancel, this));
}
void FakeAudioConsumer::Worker::DoCancel() {
void FakeAudioWorker::Worker::DoCancel() {
DCHECK(worker_task_runner_->BelongsToCurrentThread());
read_task_cb_.Cancel();
worker_task_cb_.Cancel();
}
void FakeAudioConsumer::Worker::DoRead() {
void FakeAudioWorker::Worker::DoRead() {
DCHECK(worker_task_runner_->BelongsToCurrentThread());
{
base::AutoLock scoped_lock(read_cb_lock_);
if (!read_cb_.is_null())
read_cb_.Run(audio_bus_.get());
base::AutoLock scoped_lock(worker_cb_lock_);
if (!worker_cb_.is_null())
worker_cb_.Run();
}
// Need to account for time spent here due to the cost of |read_cb_| as well
// Need to account for time spent here due to the cost of |worker_cb| as well
// as the imprecision of PostDelayedTask().
const base::TimeTicks now = base::TimeTicks::Now();
base::TimeDelta delay = next_read_time_ + buffer_duration_ - now;
......@@ -157,7 +152,7 @@ void FakeAudioConsumer::Worker::DoRead() {
next_read_time_ = now + delay;
worker_task_runner_->PostDelayedTask(
FROM_HERE, read_task_cb_.callback(), delay);
FROM_HERE, worker_task_cb_.callback(), delay);
}
} // namespace media
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_AUDIO_FAKE_AUDIO_CONSUMER_H_
#define MEDIA_AUDIO_FAKE_AUDIO_CONSUMER_H_
#ifndef MEDIA_AUDIO_FAKE_AUDIO_WORKER_H_
#define MEDIA_AUDIO_FAKE_AUDIO_WORKER_H_
#include "base/callback_forward.h"
#include "base/memory/ref_counted.h"
......@@ -17,27 +17,26 @@ namespace media {
class AudioBus;
class AudioParameters;
// A fake audio consumer. Using a provided message loop, FakeAudioConsumer will
// simulate a real time consumer of audio data.
class MEDIA_EXPORT FakeAudioConsumer {
// A fake audio worker. Using a provided message loop, FakeAudioWorker will
// call back the provided callback like a real audio consumer or producer would.
class MEDIA_EXPORT FakeAudioWorker {
public:
// |worker_task_runner| is the task runner on which the ReadCB provided to
// |worker_task_runner| is the task runner on which the closure provided to
// Start() will be executed on. This may or may not be the be for the same
// thread that invokes the Start/Stop methods.
// |params| is used to determine the frequency of callbacks.
FakeAudioConsumer(
FakeAudioWorker(
const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
const AudioParameters& params);
~FakeAudioConsumer();
~FakeAudioWorker();
// Start executing |read_cb| at a regular intervals. Stop() must be called by
// the same thread before destroying FakeAudioConsumer.
typedef base::Callback<void(AudioBus* audio_bus)> ReadCB;
void Start(const ReadCB& read_cb);
// Start executing |worker_cb| at a regular intervals. Stop() must be called
// by the same thread before destroying FakeAudioWorker.
void Start(const base::Closure& worker_cb);
// Stop executing the ReadCB provided to Start(). Blocks until the worker
// loop is not inside a ReadCB invocation. Safe to call multiple times. Must
// be called on the same thread that called Start().
// Stop executing the closure provided to Start(). Blocks until the worker
// loop is not inside a closure invocation. Safe to call multiple times.
// Must be called on the same thread that called Start().
void Stop();
private:
......@@ -47,9 +46,9 @@ class MEDIA_EXPORT FakeAudioConsumer {
class Worker;
const scoped_refptr<Worker> worker_;
DISALLOW_COPY_AND_ASSIGN(FakeAudioConsumer);
DISALLOW_COPY_AND_ASSIGN(FakeAudioWorker);
};
} // namespace media
#endif // MEDIA_AUDIO_FAKE_AUDIO_CONSUMER_H_
#endif // MEDIA_AUDIO_FAKE_AUDIO_WORKER_H_
......@@ -6,7 +6,7 @@
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/fake_audio_consumer.h"
#include "media/audio/fake_audio_worker.h"
#include "media/audio/simple_sources.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -14,57 +14,57 @@ namespace media {
static const int kTestCallbacks = 5;
class FakeAudioConsumerTest : public testing::Test {
class FakeAudioWorkerTest : public testing::Test {
public:
FakeAudioConsumerTest()
FakeAudioWorkerTest()
: params_(
AudioParameters::AUDIO_FAKE, CHANNEL_LAYOUT_STEREO, 44100, 8, 128),
fake_consumer_(message_loop_.message_loop_proxy(), params_),
source_(params_.channels(), 200.0, params_.sample_rate()) {
fake_worker_(message_loop_.message_loop_proxy(), params_),
seen_callbacks_(0) {
time_between_callbacks_ = base::TimeDelta::FromMicroseconds(
params_.frames_per_buffer() * base::Time::kMicrosecondsPerSecond /
static_cast<float>(params_.sample_rate()));
}
~FakeAudioConsumerTest() override {}
~FakeAudioWorkerTest() override {}
void ConsumeData(AudioBus* audio_bus) {
source_.OnMoreData(audio_bus, 0);
void CalledByFakeWorker() {
seen_callbacks_++;
}
void RunOnAudioThread() {
ASSERT_TRUE(message_loop_.message_loop_proxy()->BelongsToCurrentThread());
fake_consumer_.Start(base::Bind(
&FakeAudioConsumerTest::ConsumeData, base::Unretained(this)));
fake_worker_.Start(base::Bind(
&FakeAudioWorkerTest::CalledByFakeWorker, base::Unretained(this)));
}
void RunOnceOnAudioThread() {
ASSERT_TRUE(message_loop_.message_loop_proxy()->BelongsToCurrentThread());
RunOnAudioThread();
// Start() should immediately post a task to run the source callback, so we
// Start() should immediately post a task to run the callback, so we
// should end up with only a single callback being run.
message_loop_.PostTask(FROM_HERE, base::Bind(
&FakeAudioConsumerTest::EndTest, base::Unretained(this), 1));
&FakeAudioWorkerTest::EndTest, base::Unretained(this), 1));
}
void StopStartOnAudioThread() {
ASSERT_TRUE(message_loop_.message_loop_proxy()->BelongsToCurrentThread());
fake_consumer_.Stop();
fake_worker_.Stop();
RunOnAudioThread();
}
void TimeCallbacksOnAudioThread(int callbacks) {
ASSERT_TRUE(message_loop_.message_loop_proxy()->BelongsToCurrentThread());
if (source_.callbacks() == 0) {
if (seen_callbacks_ == 0) {
RunOnAudioThread();
start_time_ = base::TimeTicks::Now();
}
// Keep going until we've seen the requested number of callbacks.
if (source_.callbacks() < callbacks) {
if (seen_callbacks_ < callbacks) {
message_loop_.PostDelayedTask(FROM_HERE, base::Bind(
&FakeAudioConsumerTest::TimeCallbacksOnAudioThread,
&FakeAudioWorkerTest::TimeCallbacksOnAudioThread,
base::Unretained(this), callbacks), time_between_callbacks_ / 2);
} else {
end_time_ = base::TimeTicks::Now();
......@@ -74,46 +74,45 @@ class FakeAudioConsumerTest : public testing::Test {
void EndTest(int callbacks) {
ASSERT_TRUE(message_loop_.message_loop_proxy()->BelongsToCurrentThread());
fake_consumer_.Stop();
EXPECT_LE(callbacks, source_.callbacks());
fake_worker_.Stop();
EXPECT_LE(callbacks, seen_callbacks_);
message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
}
protected:
base::MessageLoop message_loop_;
AudioParameters params_;
FakeAudioConsumer fake_consumer_;
SineWaveAudioSource source_;
FakeAudioWorker fake_worker_;
base::TimeTicks start_time_;
base::TimeTicks end_time_;
base::TimeDelta time_between_callbacks_;
int seen_callbacks_;
private:
DISALLOW_COPY_AND_ASSIGN(FakeAudioConsumerTest);
DISALLOW_COPY_AND_ASSIGN(FakeAudioWorkerTest);
};
// Ensure the fake audio stream runs on the audio thread and handles fires
// callbacks to the AudioSourceCallback.
TEST_F(FakeAudioConsumerTest, FakeStreamBasicCallback) {
// Ensure the worker runs on the audio thread and fires callbacks.
TEST_F(FakeAudioWorkerTest, FakeBasicCallback) {
message_loop_.PostTask(FROM_HERE, base::Bind(
&FakeAudioConsumerTest::RunOnceOnAudioThread,
&FakeAudioWorkerTest::RunOnceOnAudioThread,
base::Unretained(this)));
message_loop_.Run();
}
// Ensure the time between callbacks is sane.
TEST_F(FakeAudioConsumerTest, TimeBetweenCallbacks) {
TEST_F(FakeAudioWorkerTest, TimeBetweenCallbacks) {
message_loop_.PostTask(FROM_HERE, base::Bind(
&FakeAudioConsumerTest::TimeCallbacksOnAudioThread,
&FakeAudioWorkerTest::TimeCallbacksOnAudioThread,
base::Unretained(this), kTestCallbacks));
message_loop_.Run();
// There are only (kTestCallbacks - 1) intervals between kTestCallbacks.
base::TimeDelta actual_time_between_callbacks =
(end_time_ - start_time_) / (source_.callbacks() - 1);
(end_time_ - start_time_) / (seen_callbacks_ - 1);
// Ensure callback time is no faster than the expected time between callbacks.
EXPECT_TRUE(actual_time_between_callbacks >= time_between_callbacks_);
EXPECT_GE(actual_time_between_callbacks, time_between_callbacks_);
// Softly check if the callback time is no slower than twice the expected time
// between callbacks. Since this test runs on the bots we can't be too strict
......@@ -122,17 +121,17 @@ TEST_F(FakeAudioConsumerTest, TimeBetweenCallbacks) {
LOG(ERROR) << "Time between fake audio callbacks is too large!";
}
// Ensure Start()/Stop() on the stream doesn't generate too many callbacks. See
// http://crbug.com/159049
TEST_F(FakeAudioConsumerTest, StartStopClearsCallbacks) {
// Ensure Start()/Stop() on the worker doesn't generate too many callbacks. See
// http://crbug.com/159049.
TEST_F(FakeAudioWorkerTest, StartStopClearsCallbacks) {
message_loop_.PostTask(FROM_HERE, base::Bind(
&FakeAudioConsumerTest::TimeCallbacksOnAudioThread,
&FakeAudioWorkerTest::TimeCallbacksOnAudioThread,
base::Unretained(this), kTestCallbacks));
// Issue a Stop() / Start() in between expected callbacks to maximize the
// chance of catching the FakeAudioOutputStream doing the wrong thing.
// chance of catching the worker doing the wrong thing.
message_loop_.PostDelayedTask(FROM_HERE, base::Bind(
&FakeAudioConsumerTest::StopStartOnAudioThread,
&FakeAudioWorkerTest::StopStartOnAudioThread,
base::Unretained(this)), time_between_callbacks_ / 2);
// EndTest() will ensure the proper number of callbacks have occurred.
......
......@@ -6,7 +6,7 @@
#include "base/bind.h"
#include "base/single_thread_task_runner.h"
#include "media/audio/fake_audio_consumer.h"
#include "media/audio/fake_audio_worker.h"
#include "media/base/audio_hash.h"
namespace media {
......@@ -24,7 +24,8 @@ NullAudioSink::~NullAudioSink() {}
void NullAudioSink::Initialize(const AudioParameters& params,
RenderCallback* callback) {
DCHECK(!initialized_);
fake_consumer_.reset(new FakeAudioConsumer(task_runner_, params));
fake_worker_.reset(new FakeAudioWorker(task_runner_, params));
audio_bus_ = AudioBus::Create(params);
callback_ = callback;
initialized_ = true;
}
......@@ -38,8 +39,8 @@ void NullAudioSink::Stop() {
DCHECK(task_runner_->BelongsToCurrentThread());
// Stop may be called at any time, so we have to check before stopping.
if (fake_consumer_)
fake_consumer_->Stop();
if (fake_worker_)
fake_worker_->Stop();
}
void NullAudioSink::Play() {
......@@ -49,7 +50,7 @@ void NullAudioSink::Play() {
if (playing_)
return;
fake_consumer_->Start(base::Bind(
fake_worker_->Start(base::Bind(
&NullAudioSink::CallRender, base::Unretained(this)));
playing_ = true;
}
......@@ -60,7 +61,7 @@ void NullAudioSink::Pause() {
if (!playing_)
return;
fake_consumer_->Stop();
fake_worker_->Stop();
playing_ = false;
}
......@@ -69,14 +70,14 @@ bool NullAudioSink::SetVolume(double volume) {
return volume == 0.0;
}
void NullAudioSink::CallRender(AudioBus* audio_bus) {
void NullAudioSink::CallRender() {
DCHECK(task_runner_->BelongsToCurrentThread());
int frames_received = callback_->Render(audio_bus, 0);
int frames_received = callback_->Render(audio_bus_.get(), 0);
if (!audio_hash_ || frames_received <= 0)
return;
audio_hash_->Update(audio_bus, frames_received);
audio_hash_->Update(audio_bus_.get(), frames_received);
}
void NullAudioSink::StartAudioHashForTesting() {
......
......@@ -17,7 +17,7 @@ class SingleThreadTaskRunner;
namespace media {
class AudioBus;
class AudioHash;
class FakeAudioConsumer;
class FakeAudioWorker;
class MEDIA_EXPORT NullAudioSink
: NON_EXPORTED_BASE(public AudioRendererSink) {
......@@ -44,7 +44,7 @@ class MEDIA_EXPORT NullAudioSink
private:
// Task that periodically calls Render() to consume audio data.
void CallRender(AudioBus* audio_bus);
void CallRender();
bool initialized_;
bool playing_;
......@@ -54,7 +54,8 @@ class MEDIA_EXPORT NullAudioSink
scoped_ptr<AudioHash> audio_hash_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
scoped_ptr<FakeAudioConsumer> fake_consumer_;
scoped_ptr<FakeAudioWorker> fake_worker_;
scoped_ptr<AudioBus> audio_bus_;
DISALLOW_COPY_AND_ASSIGN(NullAudioSink);
};
......
......@@ -9,10 +9,92 @@
#include <algorithm>
#include "base/files/file.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "media/audio/sounds/wav_audio_handler.h"
#include "media/base/audio_bus.h"
namespace media {
// Opens |wav_filename|, reads it and loads it as a wav file. This function will
// bluntly trigger CHECKs if we can't read the file or if it's malformed. The
// caller takes ownership of the returned data. The size of the data is stored
// in |read_length|.
static scoped_ptr<uint8[]> ReadWavFile(const base::FilePath& wav_filename,
size_t* file_length) {
base::File wav_file(
wav_filename, base::File::FLAG_OPEN | base::File::FLAG_READ);
CHECK(wav_file.IsValid())
<< "Failed to read " << wav_filename.value()
<< " as input to the fake device.";
size_t wav_file_length = wav_file.GetLength();
CHECK_GT(wav_file_length, 0u)
<< "Input file to fake device is empty: " << wav_filename.value();
uint8* wav_file_data = new uint8[wav_file_length];
size_t read_bytes = wav_file.Read(0, reinterpret_cast<char*>(wav_file_data),
wav_file_length);
CHECK_EQ(read_bytes, wav_file_length)
<< "Failed to read all bytes of " << wav_filename.value();
*file_length = wav_file_length;
return scoped_ptr<uint8[]>(wav_file_data);
}
// Opens |wav_filename|, reads it and loads it as a wav file. This function will
// bluntly trigger CHECKs if we can't read the file or if it's malformed.
static scoped_ptr<WavAudioHandler> CreateWavAudioHandler(
const base::FilePath& wav_filename, const uint8* wav_file_data,
size_t wav_file_length, const AudioParameters& expected_params) {
base::StringPiece wav_data(reinterpret_cast<const char*>(wav_file_data),
wav_file_length);
scoped_ptr<WavAudioHandler> wav_audio_handler(new WavAudioHandler(wav_data));
return wav_audio_handler.Pass();
}
// These values are based on experiments for local-to-local
// PeerConnection to demonstrate audio/video synchronization.
static const int kBeepDurationMilliseconds = 20;
static const int kBeepFrequency = 400;
// Intervals between two automatic beeps.
static const int kAutomaticBeepIntervalInMs = 500;
// Automatic beep will be triggered every |kAutomaticBeepIntervalInMs| unless
// users explicitly call BeepOnce(), which will disable the automatic beep.
class BeepContext {
public:
BeepContext() : beep_once_(false), automatic_beep_(true) {}
void SetBeepOnce(bool enable) {
base::AutoLock auto_lock(lock_);
beep_once_ = enable;
// Disable the automatic beep if users explicit set |beep_once_| to true.
if (enable)
automatic_beep_ = false;
}
bool beep_once() const {
base::AutoLock auto_lock(lock_);
return beep_once_;
}
bool automatic_beep() const {
base::AutoLock auto_lock(lock_);
return automatic_beep_;
}
private:
mutable base::Lock lock_;
bool beep_once_;
bool automatic_beep_;
};
static base::LazyInstance<BeepContext>::Leaky g_beep_context =
LAZY_INSTANCE_INITIALIZER;
//////////////////////////////////////////////////////////////////////////////
// SineWaveAudioSource implementation.
......@@ -26,6 +108,9 @@ SineWaveAudioSource::SineWaveAudioSource(int channels,
errors_(0) {
}
SineWaveAudioSource::~SineWaveAudioSource() {
}
// The implementation could be more efficient if a lookup table is constructed
// but it is efficient enough for our simple needs.
int SineWaveAudioSource::OnMoreData(AudioBus* audio_bus,
......@@ -63,4 +148,139 @@ void SineWaveAudioSource::Reset() {
time_state_ = 0;
}
FileSource::FileSource(const AudioParameters& params,
const base::FilePath& path_to_wav_file)
: params_(params),
path_to_wav_file_(path_to_wav_file),
wav_file_read_pos_(0) {
}
FileSource::~FileSource() {
}
void FileSource::LoadWavFile(const base::FilePath& path_to_wav_file) {
// Read the file, and put its data in a scoped_ptr so it gets deleted later.
size_t file_length = 0;
wav_file_data_ = ReadWavFile(path_to_wav_file, &file_length);
wav_audio_handler_ = CreateWavAudioHandler(
path_to_wav_file, wav_file_data_.get(), file_length, params_);
// Hook us up so we pull in data from the file into the converter. We need to
// modify the wav file's audio parameters since we'll be reading small slices
// of it at a time and not the whole thing (like 10 ms at a time).
AudioParameters file_audio_slice(
wav_audio_handler_->params().format(),
wav_audio_handler_->params().channel_layout(),
wav_audio_handler_->params().sample_rate(),
wav_audio_handler_->params().bits_per_sample(),
params_.frames_per_buffer());
file_audio_converter_.reset(
new AudioConverter(file_audio_slice, params_, false));
file_audio_converter_->AddInput(this);
}
int FileSource::OnMoreData(AudioBus* audio_bus, uint32 total_bytes_delay) {
// Load the file if we haven't already. This load needs to happen on the
// audio thread, otherwise we'll run on the UI thread on Mac for instance.
// This will massively delay the first OnMoreData, but we'll catch up.
if (!wav_audio_handler_)
LoadWavFile(path_to_wav_file_);
DCHECK(wav_audio_handler_.get());
// Stop playing if we've played out the whole file.
if (wav_audio_handler_->AtEnd(wav_file_read_pos_))
return 0;
// This pulls data from ProvideInput.
file_audio_converter_->Convert(audio_bus);
return audio_bus->frames();
}
double FileSource::ProvideInput(AudioBus* audio_bus_into_converter,
base::TimeDelta buffer_delay) {
// Unfilled frames will be zeroed by CopyTo.
size_t bytes_written;
wav_audio_handler_->CopyTo(audio_bus_into_converter, wav_file_read_pos_,
&bytes_written);
wav_file_read_pos_ += bytes_written;
return 1.0;
};
void FileSource::OnError(AudioOutputStream* stream) {
}
BeepingSource::BeepingSource(const AudioParameters& params)
: buffer_size_(params.GetBytesPerBuffer()),
buffer_(new uint8[buffer_size_]),
params_(params),
last_callback_time_(base::TimeTicks::Now()),
beep_duration_in_buffers_(kBeepDurationMilliseconds *
params.sample_rate() /
params.frames_per_buffer() /
1000),
beep_generated_in_buffers_(0),
beep_period_in_frames_(params.sample_rate() / kBeepFrequency) {
}
BeepingSource::~BeepingSource() {
}
int BeepingSource::OnMoreData(AudioBus* audio_bus, uint32 total_bytes_delay) {
// Accumulate the time from the last beep.
interval_from_last_beep_ += base::TimeTicks::Now() - last_callback_time_;
memset(buffer_.get(), 0, buffer_size_);
bool should_beep = false;
BeepContext* beep_context = g_beep_context.Pointer();
if (beep_context->automatic_beep()) {
base::TimeDelta delta = interval_from_last_beep_ -
base::TimeDelta::FromMilliseconds(kAutomaticBeepIntervalInMs);
if (delta > base::TimeDelta()) {
should_beep = true;
interval_from_last_beep_ = delta;
}
} else {
should_beep = beep_context->beep_once();
beep_context->SetBeepOnce(false);
}
// If this object was instructed to generate a beep or has started to
// generate a beep sound.
if (should_beep || beep_generated_in_buffers_) {
// Compute the number of frames to output high value. Then compute the
// number of bytes based on channels and bits per channel.
int high_frames = beep_period_in_frames_ / 2;
int high_bytes = high_frames * params_.bits_per_sample() *
params_.channels() / 8;
// Separate high and low with the same number of bytes to generate a
// square wave.
int position = 0;
while (position + high_bytes <= buffer_size_) {
// Write high values first.
memset(buffer_.get() + position, 128, high_bytes);
// Then leave low values in the buffer with |high_bytes|.
position += high_bytes * 2;
}
++beep_generated_in_buffers_;
if (beep_generated_in_buffers_ >= beep_duration_in_buffers_)
beep_generated_in_buffers_ = 0;
}
last_callback_time_ = base::TimeTicks::Now();
audio_bus->FromInterleaved(
buffer_.get(), audio_bus->frames(), params_.bits_per_sample() / 8);
return audio_bus->frames();
}
void BeepingSource::OnError(AudioOutputStream* stream) {
}
void BeepingSource::BeepOnce() {
g_beep_context.Pointer()->SetBeepOnce(true);
}
} // namespace media
......@@ -5,12 +5,16 @@
#ifndef MEDIA_AUDIO_SIMPLE_SOURCES_H_
#define MEDIA_AUDIO_SIMPLE_SOURCES_H_
#include "base/files/file_path.h"
#include "base/synchronization/lock.h"
#include "media/audio/audio_io.h"
#include "media/base/audio_converter.h"
#include "media/base/seekable_buffer.h"
namespace media {
class WavAudioHandler;
// An audio source that produces a pure sinusoidal tone.
class MEDIA_EXPORT SineWaveAudioSource
: public AudioOutputStream::AudioSourceCallback {
......@@ -19,7 +23,7 @@ class MEDIA_EXPORT SineWaveAudioSource
// hertz and it has to be less than half of the sampling frequency
// |sample_freq| or else you will get aliasing.
SineWaveAudioSource(int channels, double freq, double sample_freq);
~SineWaveAudioSource() override {}
~SineWaveAudioSource() override;
// Return up to |cap| samples of data via OnMoreData(). Use Reset() to
// allow more data to be served.
......@@ -44,6 +48,54 @@ class MEDIA_EXPORT SineWaveAudioSource
base::Lock time_lock_;
};
class FileSource : public AudioOutputStream::AudioSourceCallback,
public AudioConverter::InputCallback {
public:
FileSource(const AudioParameters& params,
const base::FilePath& path_to_wav_file);
~FileSource() override;
// Implementation of AudioSourceCallback.
int OnMoreData(AudioBus* audio_bus, uint32 total_bytes_delay) override;
void OnError(AudioOutputStream* stream) override;
private:
AudioParameters params_;
base::FilePath path_to_wav_file_;
scoped_ptr<uint8[]> wav_file_data_;
scoped_ptr<WavAudioHandler> wav_audio_handler_;
scoped_ptr<AudioConverter> file_audio_converter_;
int wav_file_read_pos_;
// Provides audio data from wav_audio_handler_ into the file audio converter.
double ProvideInput(AudioBus* audio_bus,
base::TimeDelta buffer_delay) override;
// Loads the wav file on the first OnMoreData invocation.
void LoadWavFile(const base::FilePath& path_to_wav_file);
};
class BeepingSource : public AudioOutputStream::AudioSourceCallback {
public:
BeepingSource(const AudioParameters& params);
~BeepingSource() override;
// Implementation of AudioSourceCallback.
int OnMoreData(AudioBus* audio_bus, uint32 total_bytes_delay) override;
void OnError(AudioOutputStream* stream) override;
static void BeepOnce();
private:
int buffer_size_;
scoped_ptr<uint8[]> buffer_;
AudioParameters params_;
base::TimeTicks last_callback_time_;
base::TimeDelta interval_from_last_beep_;
int beep_duration_in_buffers_;
int beep_generated_in_buffers_;
int beep_period_in_frames_;
};
} // namespace media
#endif // MEDIA_AUDIO_SIMPLE_SOURCES_H_
......@@ -58,7 +58,8 @@ VirtualAudioInputStream::VirtualAudioInputStream(
params_(params),
mixer_(params_, params_, false),
num_attached_output_streams_(0),
fake_consumer_(worker_task_runner_, params_) {
fake_worker_(worker_task_runner_, params_),
audio_bus_(AudioBus::Create(params)) {
DCHECK(params_.IsValid());
DCHECK(worker_task_runner_.get());
......@@ -89,13 +90,13 @@ bool VirtualAudioInputStream::Open() {
void VirtualAudioInputStream::Start(AudioInputCallback* callback) {
DCHECK(thread_checker_.CalledOnValidThread());
callback_ = callback;
fake_consumer_.Start(base::Bind(
fake_worker_.Start(base::Bind(
&VirtualAudioInputStream::PumpAudio, base::Unretained(this)));
}
void VirtualAudioInputStream::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
fake_consumer_.Stop();
fake_worker_.Stop();
callback_ = NULL;
}
......@@ -132,18 +133,18 @@ void VirtualAudioInputStream::RemoveOutputStream(
DCHECK_LE(0, num_attached_output_streams_);
}
void VirtualAudioInputStream::PumpAudio(AudioBus* audio_bus) {
void VirtualAudioInputStream::PumpAudio() {
DCHECK(worker_task_runner_->BelongsToCurrentThread());
{
base::AutoLock scoped_lock(converter_network_lock_);
// Because the audio is being looped-back, the delay until it will be played
// out is zero.
mixer_.ConvertWithDelay(base::TimeDelta(), audio_bus);
mixer_.ConvertWithDelay(base::TimeDelta(), audio_bus_.get());
}
// Because the audio is being looped-back, the delay since since it was
// recorded is zero.
callback_->OnData(this, audio_bus, 0, 1.0);
callback_->OnData(this, audio_bus_.get(), 0, 1.0);
}
void VirtualAudioInputStream::Close() {
......
......@@ -14,7 +14,7 @@
#include "base/threading/thread_checker.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/fake_audio_consumer.h"
#include "media/audio/fake_audio_worker.h"
#include "media/base/audio_converter.h"
namespace base {
......@@ -77,7 +77,7 @@ class MEDIA_EXPORT VirtualAudioInputStream : public AudioInputStream {
// Pulls audio data from all attached VirtualAudioOutputStreams, mixes and
// converts the streams into one, and pushes the result to |callback_|.
// Invoked on the worker thread.
void PumpAudio(AudioBus* audio_bus);
void PumpAudio();
const scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner_;
......@@ -105,7 +105,9 @@ class MEDIA_EXPORT VirtualAudioInputStream : public AudioInputStream {
int num_attached_output_streams_;
// Handles callback timing for consumption of audio data.
FakeAudioConsumer fake_consumer_;
FakeAudioWorker fake_worker_;
scoped_ptr<AudioBus> audio_bus_;
base::ThreadChecker thread_checker_;
......
......@@ -140,8 +140,6 @@
'audio/cras/cras_input.h',
'audio/cras/cras_unified.cc',
'audio/cras/cras_unified.h',
'audio/fake_audio_consumer.cc',
'audio/fake_audio_consumer.h',
'audio/fake_audio_input_stream.cc',
'audio/fake_audio_input_stream.h',
'audio/fake_audio_log_factory.cc',
......@@ -150,6 +148,8 @@
'audio/fake_audio_manager.h',
'audio/fake_audio_output_stream.cc',
'audio/fake_audio_output_stream.h',
'audio/fake_audio_worker.cc',
'audio/fake_audio_worker.h',
'audio/linux/audio_manager_linux.cc',
'audio/mac/audio_auhal_mac.cc',
'audio/mac/audio_auhal_mac.h',
......@@ -1135,7 +1135,7 @@
'audio/audio_output_proxy_unittest.cc',
'audio/audio_parameters_unittest.cc',
'audio/audio_power_monitor_unittest.cc',
'audio/fake_audio_consumer_unittest.cc',
'audio/fake_audio_worker_unittest.cc',
'audio/mac/audio_auhal_mac_unittest.cc',
'audio/mac/audio_device_listener_mac_unittest.cc',
'audio/mac/audio_low_latency_input_mac_unittest.cc',
......
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