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 "content/browser/renderer_host/media/audio_input_device_manager.h"
6
7#include "base/bind.h"
8#include "base/memory/scoped_ptr.h"
9#include "content/public/browser/browser_thread.h"
10#include "content/public/common/media_stream_request.h"
11#include "media/audio/audio_device_name.h"
12#include "media/audio/audio_input_ipc.h"
13#include "media/audio/audio_manager_base.h"
14#include "media/audio/audio_parameters.h"
15#include "media/base/channel_layout.h"
16#include "media/base/scoped_histogram_timer.h"
17
18namespace content {
19
20const int AudioInputDeviceManager::kFakeOpenSessionId = 1;
21
22namespace {
23// Starting id for the first capture session.
24const int kFirstSessionId = AudioInputDeviceManager::kFakeOpenSessionId + 1;
25}
26
27AudioInputDeviceManager::AudioInputDeviceManager(
28    media::AudioManager* audio_manager)
29    : listener_(NULL),
30      next_capture_session_id_(kFirstSessionId),
31      use_fake_device_(false),
32      audio_manager_(audio_manager) {
33  // TODO(xians): Remove this fake_device after the unittests do not need it.
34  StreamDeviceInfo fake_device(MEDIA_DEVICE_AUDIO_CAPTURE,
35                               media::AudioManagerBase::kDefaultDeviceName,
36                               media::AudioManagerBase::kDefaultDeviceId,
37                               44100, media::CHANNEL_LAYOUT_STEREO,
38                               0);
39  fake_device.session_id = kFakeOpenSessionId;
40  devices_.push_back(fake_device);
41}
42
43AudioInputDeviceManager::~AudioInputDeviceManager() {
44}
45
46const StreamDeviceInfo* AudioInputDeviceManager::GetOpenedDeviceInfoById(
47    int session_id) {
48  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
49  StreamDeviceList::iterator device = GetDevice(session_id);
50  if (device == devices_.end())
51    return NULL;
52
53  return &(*device);
54}
55
56void AudioInputDeviceManager::Register(
57    MediaStreamProviderListener* listener,
58    base::MessageLoopProxy* device_thread_loop) {
59  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
60  DCHECK(!listener_);
61  DCHECK(!device_loop_.get());
62  listener_ = listener;
63  device_loop_ = device_thread_loop;
64}
65
66void AudioInputDeviceManager::Unregister() {
67  DCHECK(listener_);
68  listener_ = NULL;
69}
70
71void AudioInputDeviceManager::EnumerateDevices(MediaStreamType stream_type) {
72  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
73  DCHECK(listener_);
74
75  device_loop_->PostTask(
76      FROM_HERE,
77      base::Bind(&AudioInputDeviceManager::EnumerateOnDeviceThread,
78                 this, stream_type));
79}
80
81int AudioInputDeviceManager::Open(const StreamDeviceInfo& device) {
82  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
83  // Generate a new id for this device.
84  int session_id = next_capture_session_id_++;
85  device_loop_->PostTask(
86      FROM_HERE,
87      base::Bind(&AudioInputDeviceManager::OpenOnDeviceThread,
88                 this, session_id, device));
89
90  return session_id;
91}
92
93void AudioInputDeviceManager::Close(int session_id) {
94  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
95  DCHECK(listener_);
96  StreamDeviceList::iterator device = GetDevice(session_id);
97  if (device == devices_.end())
98    return;
99  const MediaStreamType stream_type = device->device.type;
100  if (session_id != kFakeOpenSessionId)
101    devices_.erase(device);
102
103  // Post a callback through the listener on IO thread since
104  // MediaStreamManager is expecting the callback asynchronously.
105  BrowserThread::PostTask(BrowserThread::IO,
106                          FROM_HERE,
107                          base::Bind(&AudioInputDeviceManager::ClosedOnIOThread,
108                                     this, stream_type, session_id));
109}
110
111void AudioInputDeviceManager::UseFakeDevice() {
112  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
113  use_fake_device_ = true;
114}
115
116bool AudioInputDeviceManager::ShouldUseFakeDevice() const {
117  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
118  return use_fake_device_;
119}
120
121void AudioInputDeviceManager::EnumerateOnDeviceThread(
122    MediaStreamType stream_type) {
123  SCOPED_UMA_HISTOGRAM_TIMER(
124      "Media.AudioInputDeviceManager.EnumerateOnDeviceThreadTime");
125  DCHECK(IsOnDeviceThread());
126
127  media::AudioDeviceNames device_names;
128
129  switch (stream_type) {
130    case MEDIA_DEVICE_AUDIO_CAPTURE:
131      // AudioManager is guaranteed to outlive MediaStreamManager in
132      // BrowserMainloop.
133      audio_manager_->GetAudioInputDeviceNames(&device_names);
134      break;
135
136    default:
137      NOTREACHED();
138      break;
139  }
140
141  scoped_ptr<StreamDeviceInfoArray> devices(new StreamDeviceInfoArray());
142  for (media::AudioDeviceNames::iterator it = device_names.begin();
143       it != device_names.end(); ++it) {
144    // Add device information to device vector.
145    devices->push_back(StreamDeviceInfo(
146        stream_type, it->device_name, it->unique_id));
147  }
148
149  // If the |use_fake_device_| flag is on, inject the fake device if there is
150  // no available device on the OS.
151  if (use_fake_device_ && devices->empty()) {
152    devices->push_back(StreamDeviceInfo(
153        stream_type, media::AudioManagerBase::kDefaultDeviceName,
154        media::AudioManagerBase::kDefaultDeviceId));
155  }
156
157  // Return the device list through the listener by posting a task on
158  // IO thread since MediaStreamManager handles the callback asynchronously.
159  BrowserThread::PostTask(
160      BrowserThread::IO,
161      FROM_HERE,
162      base::Bind(&AudioInputDeviceManager::DevicesEnumeratedOnIOThread,
163                 this, stream_type, base::Passed(&devices)));
164}
165
166void AudioInputDeviceManager::OpenOnDeviceThread(
167    int session_id, const StreamDeviceInfo& info) {
168  SCOPED_UMA_HISTOGRAM_TIMER(
169      "Media.AudioInputDeviceManager.OpenOnDeviceThreadTime");
170  DCHECK(IsOnDeviceThread());
171
172  StreamDeviceInfo out(info.device.type, info.device.name, info.device.id,
173                       0, 0, 0);
174  out.session_id = session_id;
175
176  MediaStreamDevice::AudioDeviceParameters& input_params = out.device.input;
177
178  if (use_fake_device_) {
179    // Don't need to query the hardware information if using fake device.
180    input_params.sample_rate = 44100;
181    input_params.channel_layout = media::CHANNEL_LAYOUT_STEREO;
182  } else {
183    // Get the preferred sample rate and channel configuration for the
184    // audio device.
185    media::AudioParameters params =
186        audio_manager_->GetInputStreamParameters(info.device.id);
187    input_params.sample_rate = params.sample_rate();
188    input_params.channel_layout = params.channel_layout();
189    input_params.frames_per_buffer = params.frames_per_buffer();
190    input_params.effects = params.effects();
191
192    // Add preferred output device information if a matching output device
193    // exists.
194    out.device.matched_output_device_id =
195        audio_manager_->GetAssociatedOutputDeviceID(info.device.id);
196    if (!out.device.matched_output_device_id.empty()) {
197      params = audio_manager_->GetOutputStreamParameters(
198          out.device.matched_output_device_id);
199      MediaStreamDevice::AudioDeviceParameters& matched_output_params =
200          out.device.matched_output;
201      matched_output_params.sample_rate = params.sample_rate();
202      matched_output_params.channel_layout = params.channel_layout();
203      matched_output_params.frames_per_buffer = params.frames_per_buffer();
204    }
205  }
206
207  // Return the |session_id| through the listener by posting a task on
208  // IO thread since MediaStreamManager handles the callback asynchronously.
209  BrowserThread::PostTask(BrowserThread::IO,
210                          FROM_HERE,
211                          base::Bind(&AudioInputDeviceManager::OpenedOnIOThread,
212                                     this, session_id, out));
213}
214
215void AudioInputDeviceManager::DevicesEnumeratedOnIOThread(
216    MediaStreamType stream_type,
217    scoped_ptr<StreamDeviceInfoArray> devices) {
218  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
219  // Ensure that |devices| gets deleted on exit.
220  if (listener_)
221    listener_->DevicesEnumerated(stream_type, *devices);
222}
223
224void AudioInputDeviceManager::OpenedOnIOThread(int session_id,
225                                               const StreamDeviceInfo& info) {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
227  DCHECK_EQ(session_id, info.session_id);
228  DCHECK(GetDevice(session_id) == devices_.end());
229
230  devices_.push_back(info);
231
232  if (listener_)
233    listener_->Opened(info.device.type, session_id);
234}
235
236void AudioInputDeviceManager::ClosedOnIOThread(MediaStreamType stream_type,
237                                               int session_id) {
238  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
239  if (listener_)
240    listener_->Closed(stream_type, session_id);
241}
242
243bool AudioInputDeviceManager::IsOnDeviceThread() const {
244  return device_loop_->BelongsToCurrentThread();
245}
246
247AudioInputDeviceManager::StreamDeviceList::iterator
248AudioInputDeviceManager::GetDevice(int session_id) {
249  for (StreamDeviceList::iterator i(devices_.begin()); i != devices_.end();
250       ++i) {
251    if (i->session_id == session_id)
252      return i;
253  }
254
255  return devices_.end();
256}
257
258}  // namespace content
259