[go: nahoru, domu]

blob: 5c15ac641360103a22b15f38db46f45fc9e924b9 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/public/cpp/sounds/audio_stream_handler.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/cancelable_callback.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "media/audio/wav_audio_handler.h"
#include "media/base/channel_layout.h"
#include "media/mojo/mojom/audio_output_stream.mojom.h"
#include "media/mojo/mojom/audio_stream_factory.mojom.h"
#include "services/audio/public/cpp/output_device.h"
namespace audio {
namespace {
// Volume percent.
const double kOutputVolumePercent = 0.8;
// The number of frames each Render() call will request.
const int kDefaultFrameCount = 1024;
// Keep alive timeout for audio stream.
const int kKeepAliveMs = 1500;
AudioStreamHandler::TestObserver* g_observer_for_testing = NULL;
} // namespace
class AudioStreamHandler::AudioStreamContainer
: public media::AudioRendererSink::RenderCallback {
public:
explicit AudioStreamContainer(
SoundsManager::StreamFactoryBinder stream_factory_binder,
std::unique_ptr<media::WavAudioHandler> wav_audio)
: started_(false),
stream_factory_binder_(std::move(stream_factory_binder)),
cursor_(0),
delayed_stop_posted_(false),
wav_audio_(std::move(wav_audio)) {
DCHECK(wav_audio_);
task_runner_ = base::SequencedTaskRunnerHandle::Get();
}
AudioStreamContainer(const AudioStreamContainer&) = delete;
AudioStreamContainer& operator=(const AudioStreamContainer&) = delete;
~AudioStreamContainer() override {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
}
void Play() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
// Create OutputDevice if it is the first time playing.
if (device_ == nullptr) {
const media::AudioParameters params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Guess(wav_audio_->num_channels()),
wav_audio_->sample_rate(), kDefaultFrameCount);
if (g_observer_for_testing) {
g_observer_for_testing->Initialize(this, params);
} else {
mojo::PendingRemote<media::mojom::AudioStreamFactory> stream_factory;
stream_factory_binder_.Run(
stream_factory.InitWithNewPipeAndPassReceiver());
device_ = std::make_unique<audio::OutputDevice>(
std::move(stream_factory), params, this, std::string());
}
}
{
base::AutoLock al(state_lock_);
delayed_stop_posted_ = false;
stop_closure_.Reset(base::BindRepeating(&AudioStreamContainer::StopStream,
base::Unretained(this)));
if (started_) {
if (wav_audio_->AtEnd(cursor_))
cursor_ = 0;
return;
} else {
if (!g_observer_for_testing)
device_->SetVolume(kOutputVolumePercent);
}
cursor_ = 0;
}
started_ = true;
if (g_observer_for_testing)
g_observer_for_testing->OnPlay();
else
device_->Play();
}
void Stop() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
StopStream();
stop_closure_.Cancel();
}
private:
// media::AudioRendererSink::RenderCallback overrides:
// Following methods could be called from *ANY* thread.
int Render(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
int /* prior_frames_skipped */,
media::AudioBus* dest) override {
base::AutoLock al(state_lock_);
size_t bytes_written = 0;
if (wav_audio_->AtEnd(cursor_) ||
!wav_audio_->CopyTo(dest, cursor_, &bytes_written)) {
if (delayed_stop_posted_)
return 0;
delayed_stop_posted_ = true;
task_runner_->PostDelayedTask(FROM_HERE, stop_closure_.callback(),
base::Milliseconds(kKeepAliveMs));
return 0;
}
cursor_ += bytes_written;
return dest->frames();
}
void OnRenderError() override {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AudioStreamContainer::Stop, base::Unretained(this)));
}
void StopStream() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (started_) {
// Do not hold the |state_lock_| while stopping the output stream.
if (g_observer_for_testing)
g_observer_for_testing->OnStop(cursor_);
else
device_->Pause();
}
started_ = false;
}
bool started_;
const SoundsManager::StreamFactoryBinder stream_factory_binder_;
std::unique_ptr<audio::OutputDevice> device_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
// All variables below must be accessed under |state_lock_| when |started_|.
base::Lock state_lock_;
size_t cursor_;
bool delayed_stop_posted_;
std::unique_ptr<media::WavAudioHandler> wav_audio_;
base::CancelableRepeatingClosure stop_closure_;
};
AudioStreamHandler::AudioStreamHandler(
SoundsManager::StreamFactoryBinder stream_factory_binder,
const base::StringPiece& wav_data) {
task_runner_ = base::SequencedTaskRunnerHandle::Get();
std::unique_ptr<media::WavAudioHandler> wav_audio =
media::WavAudioHandler::Create(wav_data);
if (!wav_audio) {
LOG(ERROR) << "wav_data is not valid";
return;
}
const media::AudioParameters params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Guess(wav_audio->num_channels()),
wav_audio->sample_rate(), kDefaultFrameCount);
if (!params.IsValid()) {
LOG(ERROR) << "Audio params are invalid.";
return;
}
// Store the duration of the WAV data then pass the handler to |stream_|.
duration_ = wav_audio->GetDuration();
stream_ = std::make_unique<AudioStreamContainer>(
std::move(stream_factory_binder), std::move(wav_audio));
}
AudioStreamHandler::~AudioStreamHandler() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (IsInitialized()) {
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&AudioStreamContainer::Stop,
base::Unretained(stream_.get())));
task_runner_->DeleteSoon(FROM_HERE, stream_.release());
}
}
bool AudioStreamHandler::IsInitialized() const {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
return !!stream_;
}
bool AudioStreamHandler::Play() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (!IsInitialized())
return false;
task_runner_->PostTask(
FROM_HERE, base::BindOnce(base::IgnoreResult(&AudioStreamContainer::Play),
base::Unretained(stream_.get())));
return true;
}
void AudioStreamHandler::Stop() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (!IsInitialized())
return;
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&AudioStreamContainer::Stop,
base::Unretained(stream_.get())));
}
base::TimeDelta AudioStreamHandler::duration() const {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
return duration_;
}
// static
void AudioStreamHandler::SetObserverForTesting(TestObserver* observer) {
g_observer_for_testing = observer;
}
} // namespace audio