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/single_thread_task_runner.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 base::TimeDelta& close_delay)
23    : AudioOutputDispatcher(audio_manager,
24                            params,
25                            output_device_id),
26      idle_proxies_(0),
27      close_timer_(FROM_HERE,
28                   close_delay,
29                   this,
30                   &AudioOutputDispatcherImpl::CloseAllIdleStreams),
31      audio_log_(
32          audio_manager->CreateAudioLog(AudioLogFactory::AUDIO_OUTPUT_STREAM)),
33      audio_stream_id_(0) {}
34
35AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
36  DCHECK_EQ(idle_proxies_, 0u);
37  DCHECK(proxy_to_physical_map_.empty());
38  DCHECK(idle_streams_.empty());
39}
40
41bool AudioOutputDispatcherImpl::OpenStream() {
42  DCHECK(task_runner_->BelongsToCurrentThread());
43
44  // Ensure that there is at least one open stream.
45  if (idle_streams_.empty() && !CreateAndOpenStream())
46    return false;
47
48  ++idle_proxies_;
49  close_timer_.Reset();
50  return true;
51}
52
53bool AudioOutputDispatcherImpl::StartStream(
54    AudioOutputStream::AudioSourceCallback* callback,
55    AudioOutputProxy* stream_proxy) {
56  DCHECK(task_runner_->BelongsToCurrentThread());
57  DCHECK(proxy_to_physical_map_.find(stream_proxy) ==
58         proxy_to_physical_map_.end());
59
60  if (idle_streams_.empty() && !CreateAndOpenStream())
61    return false;
62
63  AudioOutputStream* physical_stream = idle_streams_.back();
64  idle_streams_.pop_back();
65
66  DCHECK_GT(idle_proxies_, 0u);
67  --idle_proxies_;
68
69  double volume = 0;
70  stream_proxy->GetVolume(&volume);
71  physical_stream->SetVolume(volume);
72  const int stream_id = audio_stream_ids_[physical_stream];
73  audio_log_->OnSetVolume(stream_id, volume);
74  physical_stream->Start(callback);
75  audio_log_->OnStarted(stream_id);
76  proxy_to_physical_map_[stream_proxy] = physical_stream;
77
78  close_timer_.Reset();
79  return true;
80}
81
82void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
83  DCHECK(task_runner_->BelongsToCurrentThread());
84
85  AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
86  DCHECK(it != proxy_to_physical_map_.end());
87  AudioOutputStream* physical_stream = it->second;
88  proxy_to_physical_map_.erase(it);
89
90  physical_stream->Stop();
91  audio_log_->OnStopped(audio_stream_ids_[physical_stream]);
92  ++idle_proxies_;
93  idle_streams_.push_back(physical_stream);
94
95  close_timer_.Reset();
96}
97
98void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
99                                                double volume) {
100  DCHECK(task_runner_->BelongsToCurrentThread());
101  AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
102  if (it != proxy_to_physical_map_.end()) {
103    AudioOutputStream* physical_stream = it->second;
104    physical_stream->SetVolume(volume);
105    audio_log_->OnSetVolume(audio_stream_ids_[physical_stream], volume);
106  }
107}
108
109void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
110  DCHECK(task_runner_->BelongsToCurrentThread());
111
112  DCHECK_GT(idle_proxies_, 0u);
113  --idle_proxies_;
114
115  // Leave at least a single stream running until the close timer fires to help
116  // cycle time when streams are opened and closed repeatedly.
117  CloseIdleStreams(std::max(idle_proxies_, static_cast<size_t>(1)));
118  close_timer_.Reset();
119}
120
121void AudioOutputDispatcherImpl::Shutdown() {
122  DCHECK(task_runner_->BelongsToCurrentThread());
123
124  // Close all idle streams immediately.  The |close_timer_| will handle
125  // invalidating any outstanding tasks upon its destruction.
126  CloseAllIdleStreams();
127
128  // No AudioOutputProxy objects should hold a reference to us when we get
129  // to this stage.
130  DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
131}
132
133bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
134  DCHECK(task_runner_->BelongsToCurrentThread());
135  AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
136      params_, device_id_);
137  if (!stream)
138    return false;
139
140  if (!stream->Open()) {
141    stream->Close();
142    return false;
143  }
144
145  const int stream_id = audio_stream_id_++;
146  audio_stream_ids_[stream] = stream_id;
147  audio_log_->OnCreated(
148      stream_id, params_, device_id_);
149
150  idle_streams_.push_back(stream);
151  return true;
152}
153
154void AudioOutputDispatcherImpl::CloseAllIdleStreams() {
155  DCHECK(task_runner_->BelongsToCurrentThread());
156  CloseIdleStreams(0);
157}
158
159void AudioOutputDispatcherImpl::CloseIdleStreams(size_t keep_alive) {
160  DCHECK(task_runner_->BelongsToCurrentThread());
161  if (idle_streams_.size() <= keep_alive)
162    return;
163  for (size_t i = keep_alive; i < idle_streams_.size(); ++i) {
164    AudioOutputStream* stream = idle_streams_[i];
165    stream->Close();
166
167    AudioStreamIDMap::iterator it = audio_stream_ids_.find(stream);
168    DCHECK(it != audio_stream_ids_.end());
169    audio_log_->OnClosed(it->second);
170    audio_stream_ids_.erase(it);
171  }
172  idle_streams_.erase(idle_streams_.begin() + keep_alive, idle_streams_.end());
173}
174
175}  // namespace media
176