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