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_input_ipc.h"
12#include "media/audio/audio_manager_base.h"
13#include "media/audio/audio_parameters.h"
14#include "media/base/channel_layout.h"
15#include "media/base/scoped_histogram_timer.h"
16
17#if defined(OS_CHROMEOS)
18#include "chromeos/audio/cras_audio_handler.h"
19#endif
20
21namespace content {
22
23const int AudioInputDeviceManager::kFakeOpenSessionId = 1;
24
25namespace {
26// Starting id for the first capture session.
27const int kFirstSessionId = AudioInputDeviceManager::kFakeOpenSessionId + 1;
28}
29
30AudioInputDeviceManager::AudioInputDeviceManager(
31    media::AudioManager* audio_manager)
32    : listener_(NULL),
33      next_capture_session_id_(kFirstSessionId),
34      use_fake_device_(false),
35#if defined(OS_CHROMEOS)
36      keyboard_mic_streams_count_(0),
37#endif
38      audio_manager_(audio_manager) {
39}
40
41AudioInputDeviceManager::~AudioInputDeviceManager() {
42}
43
44const StreamDeviceInfo* AudioInputDeviceManager::GetOpenedDeviceInfoById(
45    int session_id) {
46  DCHECK_CURRENTLY_ON(BrowserThread::IO);
47  StreamDeviceList::iterator device = GetDevice(session_id);
48  if (device == devices_.end())
49    return NULL;
50
51  return &(*device);
52}
53
54void AudioInputDeviceManager::Register(
55    MediaStreamProviderListener* listener,
56    const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
57  DCHECK_CURRENTLY_ON(BrowserThread::IO);
58  DCHECK(!listener_);
59  DCHECK(!device_task_runner_.get());
60  listener_ = listener;
61  device_task_runner_ = device_task_runner;
62}
63
64void AudioInputDeviceManager::Unregister() {
65  DCHECK(listener_);
66  listener_ = NULL;
67}
68
69void AudioInputDeviceManager::EnumerateDevices(MediaStreamType stream_type) {
70  DCHECK_CURRENTLY_ON(BrowserThread::IO);
71  DCHECK(listener_);
72
73  device_task_runner_->PostTask(
74      FROM_HERE,
75      base::Bind(&AudioInputDeviceManager::EnumerateOnDeviceThread,
76                 this, stream_type));
77}
78
79int AudioInputDeviceManager::Open(const StreamDeviceInfo& device) {
80  DCHECK_CURRENTLY_ON(BrowserThread::IO);
81  // Generate a new id for this device.
82  int session_id = next_capture_session_id_++;
83  device_task_runner_->PostTask(
84      FROM_HERE,
85      base::Bind(&AudioInputDeviceManager::OpenOnDeviceThread,
86                 this, session_id, device));
87
88  return session_id;
89}
90
91void AudioInputDeviceManager::Close(int session_id) {
92  DCHECK_CURRENTLY_ON(BrowserThread::IO);
93  DCHECK(listener_);
94  StreamDeviceList::iterator device = GetDevice(session_id);
95  if (device == devices_.end())
96    return;
97  const MediaStreamType stream_type = device->device.type;
98  if (session_id != kFakeOpenSessionId)
99    devices_.erase(device);
100
101  // Post a callback through the listener on IO thread since
102  // MediaStreamManager is expecting the callback asynchronously.
103  BrowserThread::PostTask(BrowserThread::IO,
104                          FROM_HERE,
105                          base::Bind(&AudioInputDeviceManager::ClosedOnIOThread,
106                                     this, stream_type, session_id));
107}
108
109void AudioInputDeviceManager::UseFakeDevice() {
110  DCHECK_CURRENTLY_ON(BrowserThread::IO);
111  use_fake_device_ = true;
112}
113
114bool AudioInputDeviceManager::ShouldUseFakeDevice() const {
115  DCHECK_CURRENTLY_ON(BrowserThread::IO);
116  return use_fake_device_;
117}
118
119#if defined(OS_CHROMEOS)
120void AudioInputDeviceManager::RegisterKeyboardMicStream(
121    const base::Closure& callback) {
122  DCHECK_CURRENTLY_ON(BrowserThread::IO);
123
124  ++keyboard_mic_streams_count_;
125  if (keyboard_mic_streams_count_ == 1) {
126    BrowserThread::PostTaskAndReply(
127        BrowserThread::UI,
128        FROM_HERE,
129        base::Bind(
130            &AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread,
131            this,
132            true),
133        callback);
134  } else {
135    callback.Run();
136  }
137}
138
139void AudioInputDeviceManager::UnregisterKeyboardMicStream() {
140  DCHECK_CURRENTLY_ON(BrowserThread::IO);
141
142  --keyboard_mic_streams_count_;
143  DCHECK_GE(keyboard_mic_streams_count_, 0);
144  if (keyboard_mic_streams_count_ == 0) {
145    BrowserThread::PostTask(
146        BrowserThread::UI,
147        FROM_HERE,
148        base::Bind(
149            &AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread,
150            this,
151            false));
152  }
153}
154#endif
155
156void AudioInputDeviceManager::EnumerateOnDeviceThread(
157    MediaStreamType stream_type) {
158  SCOPED_UMA_HISTOGRAM_TIMER(
159      "Media.AudioInputDeviceManager.EnumerateOnDeviceThreadTime");
160  DCHECK(IsOnDeviceThread());
161  DCHECK_EQ(MEDIA_DEVICE_AUDIO_CAPTURE, stream_type);
162
163  media::AudioDeviceNames device_names;
164  if (use_fake_device_) {
165    // Use the fake devices.
166    GetFakeDeviceNames(&device_names);
167  } else {
168    // Enumerate the devices on the OS.
169    // AudioManager is guaranteed to outlive MediaStreamManager in
170    // BrowserMainloop.
171    audio_manager_->GetAudioInputDeviceNames(&device_names);
172  }
173
174  scoped_ptr<StreamDeviceInfoArray> devices(new StreamDeviceInfoArray());
175  for (media::AudioDeviceNames::iterator it = device_names.begin();
176       it != device_names.end(); ++it) {
177    // Add device information to device vector.
178    devices->push_back(StreamDeviceInfo(
179        stream_type, it->device_name, it->unique_id));
180  }
181
182  // Return the device list through the listener by posting a task on
183  // IO thread since MediaStreamManager handles the callback asynchronously.
184  BrowserThread::PostTask(
185      BrowserThread::IO,
186      FROM_HERE,
187      base::Bind(&AudioInputDeviceManager::DevicesEnumeratedOnIOThread,
188                 this, stream_type, base::Passed(&devices)));
189}
190
191void AudioInputDeviceManager::OpenOnDeviceThread(
192    int session_id, const StreamDeviceInfo& info) {
193  SCOPED_UMA_HISTOGRAM_TIMER(
194      "Media.AudioInputDeviceManager.OpenOnDeviceThreadTime");
195  DCHECK(IsOnDeviceThread());
196
197  StreamDeviceInfo out(info.device.type, info.device.name, info.device.id,
198                       0, 0, 0);
199  out.session_id = session_id;
200
201  MediaStreamDevice::AudioDeviceParameters& input_params = out.device.input;
202
203  if (use_fake_device_) {
204    // Don't need to query the hardware information if using fake device.
205    input_params.sample_rate = 44100;
206    input_params.channel_layout = media::CHANNEL_LAYOUT_STEREO;
207  } else {
208    // Get the preferred sample rate and channel configuration for the
209    // audio device.
210    media::AudioParameters params =
211        audio_manager_->GetInputStreamParameters(info.device.id);
212    input_params.sample_rate = params.sample_rate();
213    input_params.channel_layout = params.channel_layout();
214    input_params.frames_per_buffer = params.frames_per_buffer();
215    input_params.effects = params.effects();
216
217    // Add preferred output device information if a matching output device
218    // exists.
219    out.device.matched_output_device_id =
220        audio_manager_->GetAssociatedOutputDeviceID(info.device.id);
221    if (!out.device.matched_output_device_id.empty()) {
222      params = audio_manager_->GetOutputStreamParameters(
223          out.device.matched_output_device_id);
224      MediaStreamDevice::AudioDeviceParameters& matched_output_params =
225          out.device.matched_output;
226      matched_output_params.sample_rate = params.sample_rate();
227      matched_output_params.channel_layout = params.channel_layout();
228      matched_output_params.frames_per_buffer = params.frames_per_buffer();
229    }
230  }
231
232  // Return the |session_id| through the listener by posting a task on
233  // IO thread since MediaStreamManager handles the callback asynchronously.
234  BrowserThread::PostTask(BrowserThread::IO,
235                          FROM_HERE,
236                          base::Bind(&AudioInputDeviceManager::OpenedOnIOThread,
237                                     this, session_id, out));
238}
239
240void AudioInputDeviceManager::DevicesEnumeratedOnIOThread(
241    MediaStreamType stream_type,
242    scoped_ptr<StreamDeviceInfoArray> devices) {
243  DCHECK_CURRENTLY_ON(BrowserThread::IO);
244  // Ensure that |devices| gets deleted on exit.
245  if (listener_)
246    listener_->DevicesEnumerated(stream_type, *devices);
247}
248
249void AudioInputDeviceManager::OpenedOnIOThread(int session_id,
250                                               const StreamDeviceInfo& info) {
251  DCHECK_CURRENTLY_ON(BrowserThread::IO);
252  DCHECK_EQ(session_id, info.session_id);
253  DCHECK(GetDevice(session_id) == devices_.end());
254
255  devices_.push_back(info);
256
257  if (listener_)
258    listener_->Opened(info.device.type, session_id);
259}
260
261void AudioInputDeviceManager::ClosedOnIOThread(MediaStreamType stream_type,
262                                               int session_id) {
263  DCHECK_CURRENTLY_ON(BrowserThread::IO);
264  if (listener_)
265    listener_->Closed(stream_type, session_id);
266}
267
268bool AudioInputDeviceManager::IsOnDeviceThread() const {
269  return device_task_runner_->BelongsToCurrentThread();
270}
271
272AudioInputDeviceManager::StreamDeviceList::iterator
273AudioInputDeviceManager::GetDevice(int session_id) {
274  for (StreamDeviceList::iterator i(devices_.begin()); i != devices_.end();
275       ++i) {
276    if (i->session_id == session_id)
277      return i;
278  }
279
280  return devices_.end();
281}
282
283void AudioInputDeviceManager::GetFakeDeviceNames(
284    media::AudioDeviceNames* device_names) {
285  static const char kFakeDeviceName1[] = "Fake Audio 1";
286  static const char kFakeDeviceId1[] = "fake_audio_1";
287  static const char kFakeDeviceName2[] = "Fake Audio 2";
288  static const char kFakeDeviceId2[] = "fake_audio_2";
289  DCHECK(device_names->empty());
290  DCHECK(use_fake_device_);
291  device_names->push_back(media::AudioDeviceName(kFakeDeviceName1,
292                                                 kFakeDeviceId1));
293  device_names->push_back(media::AudioDeviceName(kFakeDeviceName2,
294                                                 kFakeDeviceId2));
295}
296
297#if defined(OS_CHROMEOS)
298void AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread(
299    bool active) {
300  DCHECK_CURRENTLY_ON(BrowserThread::UI);
301  chromeos::CrasAudioHandler::Get()->SetKeyboardMicActive(active);
302}
303#endif
304
305
306}  // namespace content
307