audio_output_dispatcher_impl.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright (c) 2012 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 "media/audio/audio_output_dispatcher_impl.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/compiler_specific.h"
11#include "base/message_loop/message_loop.h"
12#include "base/time/time.h"
13#include "media/audio/audio_io.h"
14#include "media/audio/audio_output_proxy.h"
15
16namespace media {
17
18AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
19    AudioManager* audio_manager,
20    const AudioParameters& params,
21    const std::string& output_device_id,
22    const std::string& input_device_id,
23    const base::TimeDelta& close_delay)
24    : AudioOutputDispatcher(audio_manager, params, output_device_id,
25          input_device_id),
26      pause_delay_(base::TimeDelta::FromMicroseconds(
27          2 * params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond /
28          static_cast<float>(params.sample_rate()))),
29      paused_proxies_(0),
30      weak_this_(this),
31      close_timer_(FROM_HERE,
32                   close_delay,
33                   this,
34                   &AudioOutputDispatcherImpl::ClosePendingStreams) {
35}
36
37AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
38  DCHECK(proxy_to_physical_map_.empty());
39  DCHECK(idle_streams_.empty());
40  DCHECK(pausing_streams_.empty());
41}
42
43bool AudioOutputDispatcherImpl::OpenStream() {
44  DCHECK(message_loop_->BelongsToCurrentThread());
45
46  paused_proxies_++;
47
48  // Ensure that there is at least one open stream.
49  if (idle_streams_.empty() && !CreateAndOpenStream()) {
50    paused_proxies_--;
51    return false;
52  }
53
54  close_timer_.Reset();
55  return true;
56}
57
58bool AudioOutputDispatcherImpl::StartStream(
59    AudioOutputStream::AudioSourceCallback* callback,
60    AudioOutputProxy* stream_proxy) {
61  DCHECK(message_loop_->BelongsToCurrentThread());
62
63  if (idle_streams_.empty() && !CreateAndOpenStream())
64    return false;
65
66  AudioOutputStream* physical_stream = idle_streams_.back();
67  DCHECK(physical_stream);
68  idle_streams_.pop_back();
69
70  DCHECK_GT(paused_proxies_, 0u);
71  --paused_proxies_;
72
73  close_timer_.Reset();
74
75  // Schedule task to allocate streams for other proxies if we need to.
76  message_loop_->PostTask(FROM_HERE, base::Bind(
77      &AudioOutputDispatcherImpl::OpenTask, weak_this_.GetWeakPtr()));
78
79  double volume = 0;
80  stream_proxy->GetVolume(&volume);
81  physical_stream->SetVolume(volume);
82  physical_stream->Start(callback);
83  proxy_to_physical_map_[stream_proxy] = physical_stream;
84  return true;
85}
86
87void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
88  DCHECK(message_loop_->BelongsToCurrentThread());
89
90  AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
91  DCHECK(it != proxy_to_physical_map_.end());
92  AudioOutputStream* physical_stream = it->second;
93  proxy_to_physical_map_.erase(it);
94
95  physical_stream->Stop();
96
97  ++paused_proxies_;
98
99  pausing_streams_.push_front(physical_stream);
100
101  // Don't recycle stream until two buffers worth of time has elapsed.
102  message_loop_->PostDelayedTask(
103      FROM_HERE,
104      base::Bind(&AudioOutputDispatcherImpl::StopStreamTask,
105                 weak_this_.GetWeakPtr()),
106      pause_delay_);
107}
108
109void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
110                                                double volume) {
111  DCHECK(message_loop_->BelongsToCurrentThread());
112  AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
113  if (it != proxy_to_physical_map_.end()) {
114    AudioOutputStream* physical_stream = it->second;
115    physical_stream->SetVolume(volume);
116  }
117}
118
119void AudioOutputDispatcherImpl::StopStreamTask() {
120  DCHECK(message_loop_->BelongsToCurrentThread());
121
122  if (pausing_streams_.empty())
123    return;
124
125  AudioOutputStream* stream = pausing_streams_.back();
126  pausing_streams_.pop_back();
127  idle_streams_.push_back(stream);
128  close_timer_.Reset();
129}
130
131void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
132  DCHECK(message_loop_->BelongsToCurrentThread());
133
134  while (!pausing_streams_.empty()) {
135    idle_streams_.push_back(pausing_streams_.back());
136    pausing_streams_.pop_back();
137  }
138
139  DCHECK_GT(paused_proxies_, 0u);
140  paused_proxies_--;
141
142  while (idle_streams_.size() > paused_proxies_) {
143    idle_streams_.back()->Close();
144    idle_streams_.pop_back();
145  }
146}
147
148void AudioOutputDispatcherImpl::Shutdown() {
149  DCHECK(message_loop_->BelongsToCurrentThread());
150
151  // Cancel any pending tasks to close paused streams or create new ones.
152  weak_this_.InvalidateWeakPtrs();
153
154  // No AudioOutputProxy objects should hold a reference to us when we get
155  // to this stage.
156  DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
157
158  AudioOutputStreamList::iterator it = idle_streams_.begin();
159  for (; it != idle_streams_.end(); ++it)
160    (*it)->Close();
161  idle_streams_.clear();
162
163  it = pausing_streams_.begin();
164  for (; it != pausing_streams_.end(); ++it)
165    (*it)->Close();
166  pausing_streams_.clear();
167}
168
169bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
170  DCHECK(message_loop_->BelongsToCurrentThread());
171  AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
172      params_, output_device_id_, input_device_id_);
173  if (!stream)
174    return false;
175
176  if (!stream->Open()) {
177    stream->Close();
178    return false;
179  }
180  idle_streams_.push_back(stream);
181  return true;
182}
183
184void AudioOutputDispatcherImpl::OpenTask() {
185  DCHECK(message_loop_->BelongsToCurrentThread());
186  // Make sure that we have at least one stream allocated if there
187  // are paused streams.
188  if (paused_proxies_ > 0 && idle_streams_.empty() &&
189      pausing_streams_.empty()) {
190    CreateAndOpenStream();
191  }
192
193  close_timer_.Reset();
194}
195
196// This method is called by |close_timer_|.
197void AudioOutputDispatcherImpl::ClosePendingStreams() {
198  DCHECK(message_loop_->BelongsToCurrentThread());
199  while (!idle_streams_.empty()) {
200    idle_streams_.back()->Close();
201    idle_streams_.pop_back();
202  }
203}
204
205}  // namespace media
206