1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/copresence/mediums/audio/audio_player.h"
6
7#include <algorithm>
8#include <string>
9
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/logging.h"
13#include "base/run_loop.h"
14#include "components/copresence/public/copresence_constants.h"
15#include "content/public/browser/browser_thread.h"
16#include "media/audio/audio_manager.h"
17#include "media/audio/audio_parameters.h"
18#include "media/base/audio_bus.h"
19
20namespace {
21
22const int kDefaultFrameCount = 1024;
23const double kOutputVolumePercent = 1.0f;
24
25}  // namespace
26
27namespace copresence {
28
29// Public methods.
30
31AudioPlayer::AudioPlayer()
32    : is_playing_(false), stream_(NULL), frame_index_(0) {
33}
34
35AudioPlayer::~AudioPlayer() {
36}
37
38void AudioPlayer::Initialize() {
39  media::AudioManager::Get()->GetTaskRunner()->PostTask(
40      FROM_HERE,
41      base::Bind(&AudioPlayer::InitializeOnAudioThread,
42                 base::Unretained(this)));
43}
44
45void AudioPlayer::Play(
46    const scoped_refptr<media::AudioBusRefCounted>& samples) {
47  media::AudioManager::Get()->GetTaskRunner()->PostTask(
48      FROM_HERE,
49      base::Bind(
50          &AudioPlayer::PlayOnAudioThread, base::Unretained(this), samples));
51}
52
53void AudioPlayer::Stop() {
54  media::AudioManager::Get()->GetTaskRunner()->PostTask(
55      FROM_HERE,
56      base::Bind(&AudioPlayer::StopOnAudioThread, base::Unretained(this)));
57}
58
59bool AudioPlayer::IsPlaying() {
60  return is_playing_;
61}
62
63void AudioPlayer::Finalize() {
64  media::AudioManager::Get()->GetTaskRunner()->PostTask(
65      FROM_HERE,
66      base::Bind(&AudioPlayer::FinalizeOnAudioThread, base::Unretained(this)));
67}
68
69// Private methods.
70
71void AudioPlayer::InitializeOnAudioThread() {
72  DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
73  stream_ = output_stream_for_testing_
74                ? output_stream_for_testing_.get()
75                : media::AudioManager::Get()->MakeAudioOutputStreamProxy(
76                      media::AudioParameters(
77                          media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
78                          media::CHANNEL_LAYOUT_MONO,
79                          kDefaultSampleRate,
80                          kDefaultBitsPerSample,
81                          kDefaultFrameCount),
82                      std::string());
83
84  if (!stream_ || !stream_->Open()) {
85    LOG(ERROR) << "Failed to open an output stream.";
86    if (stream_) {
87      stream_->Close();
88      stream_ = NULL;
89    }
90    return;
91  }
92  stream_->SetVolume(kOutputVolumePercent);
93}
94
95void AudioPlayer::PlayOnAudioThread(
96    const scoped_refptr<media::AudioBusRefCounted>& samples) {
97  DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
98  if (!stream_)
99    return;
100
101  {
102    base::AutoLock al(state_lock_);
103
104    samples_ = samples;
105    frame_index_ = 0;
106
107    if (is_playing_)
108      return;
109  }
110
111  is_playing_ = true;
112  stream_->Start(this);
113}
114
115void AudioPlayer::StopOnAudioThread() {
116  DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
117  if (!stream_)
118    return;
119
120  stream_->Stop();
121  is_playing_ = false;
122}
123
124void AudioPlayer::StopAndCloseOnAudioThread() {
125  DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
126  if (!stream_)
127    return;
128
129  if (is_playing_)
130    stream_->Stop();
131  stream_->Close();
132  stream_ = NULL;
133
134  is_playing_ = false;
135}
136
137void AudioPlayer::FinalizeOnAudioThread() {
138  DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
139  StopAndCloseOnAudioThread();
140  delete this;
141}
142
143int AudioPlayer::OnMoreData(media::AudioBus* dest,
144                            media::AudioBuffersState /* state */) {
145  base::AutoLock al(state_lock_);
146  // Continuously play our samples till explicitly told to stop.
147  const int leftover_frames = samples_->frames() - frame_index_;
148  const int frames_to_copy = std::min(dest->frames(), leftover_frames);
149
150  samples_->CopyPartialFramesTo(frame_index_, frames_to_copy, 0, dest);
151  frame_index_ += frames_to_copy;
152
153  // If we didn't fill the destination audio bus, wrap around and fill the rest.
154  if (leftover_frames <= dest->frames()) {
155    samples_->CopyPartialFramesTo(
156        0, dest->frames() - frames_to_copy, frames_to_copy, dest);
157    frame_index_ = dest->frames() - frames_to_copy;
158  }
159
160  return dest->frames();
161}
162
163void AudioPlayer::OnError(media::AudioOutputStream* /* stream */) {
164  LOG(ERROR) << "Error during system sound reproduction.";
165  media::AudioManager::Get()->GetTaskRunner()->PostTask(
166      FROM_HERE,
167      base::Bind(&AudioPlayer::StopAndCloseOnAudioThread,
168                 base::Unretained(this)));
169}
170
171void AudioPlayer::FlushAudioLoopForTesting() {
172  if (media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread())
173    return;
174
175  // Queue task on the audio thread, when it is executed, that means we've
176  // successfully executed all the tasks before us.
177  base::RunLoop rl;
178  media::AudioManager::Get()->GetTaskRunner()->PostTaskAndReply(
179      FROM_HERE,
180      base::Bind(base::IgnoreResult(&AudioPlayer::FlushAudioLoopForTesting),
181                 base::Unretained(this)),
182      rl.QuitClosure());
183  rl.Run();
184}
185
186}  // namespace copresence
187