summaryrefslogtreecommitdiff
path: root/chromium/media/audio/win/audio_manager_win.cc
blob: f7efeff67fa3e538b7ed2857eeb0e0797180452b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/audio/win/audio_manager_win.h"

#include <windows.h>

#include <objbase.h>  // This has to be before initguid.h

#include <initguid.h>
#include <mmsystem.h>
#include <setupapi.h>
#include <stddef.h>

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/win/windows_version.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_io.h"
#include "media/audio/win/audio_device_listener_win.h"
#include "media/audio/win/audio_low_latency_input_win.h"
#include "media/audio/win/audio_low_latency_output_win.h"
#include "media/audio/win/core_audio_util_win.h"
#include "media/audio/win/device_enumeration_win.h"
#include "media/audio/win/waveout_output_win.h"
#include "media/base/audio_parameters.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/channel_layout.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"

// The following are defined in various DDK headers, and we (re)define them here
// to avoid adding the DDK as a chrome dependency.
#define DRV_QUERYDEVICEINTERFACE 0x80c
#define DRVM_MAPPER_PREFERRED_GET 0x2015
#define DRV_QUERYDEVICEINTERFACESIZE 0x80d
DEFINE_GUID(AM_KSCATEGORY_AUDIO,
            0x6994ad04,
            0x93ef,
            0x11d0,
            0xa3,
            0xcc,
            0x00,
            0xa0,
            0xc9,
            0x22,
            0x31,
            0x96);

namespace media {

// Maximum number of output streams that can be open simultaneously.
static const int kMaxOutputStreams = 50;

// Up to 8 channels can be passed to the driver.  This should work, given the
// right drivers, but graceful error handling is needed.
static const int kWinMaxChannels = 8;

// Buffer size to use for input and output stream when a proper size can't be
// determined from the system
static const int kFallbackBufferSize = 2048;

static int NumberOfWaveOutBuffers() {
  // Use the user provided buffer count if provided.
  int buffers = 0;
  std::string buffers_str(
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kWaveOutBuffers));
  if (base::StringToInt(buffers_str, &buffers) && buffers > 0) {
    return buffers;
  }

  return 3;
}

static bool IsSupported(HRESULT hr) {
  return hr != S_FALSE && SUCCEEDED(hr);
}

// Records bitstream output support to histograms. Follows information from:
// https://docs.microsoft.com/en-us/windows/desktop/coreaudio/representing-formats-for-iec-61937-transmissions
static void LogBitstreamOutputSupport() {
  auto client = CoreAudioUtil::CreateClient(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole);

  // Happens if no audio output devices are available.
  if (!client)
    return;

  WAVEFORMATEXTENSIBLE wfext;
  memset(&wfext, 0, sizeof(wfext));

  // See link in function comment for where each value comes from.
  wfext.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  wfext.Format.nChannels = 2;
  wfext.Format.nSamplesPerSec = 192000;
  wfext.Format.nAvgBytesPerSec = 768000;
  wfext.Format.nBlockAlign = 4;
  wfext.Format.wBitsPerSample = 16;
  wfext.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
  wfext.Samples.wValidBitsPerSample = 16;
  wfext.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;

  // Test Dolby Digital+ / Atmos support. For whatever reason Atmos doesn't use
  // the KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS_ATMOS SubFormat.
  wfext.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;

  HRESULT hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE,
                                         &wfext.Format, nullptr);
  base::UmaHistogramBoolean("Media.Audio.Bitstream.EAC3", IsSupported(hr));

  // Test Dolby TrueHD.
  wfext.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
  hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfext.Format,
                                 nullptr);
  base::UmaHistogramBoolean("Media.Audio.Bitstream.TrueHD", IsSupported(hr));

  // Test DTS-HD.
  wfext.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
  hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfext.Format,
                                 nullptr);
  base::UmaHistogramBoolean("Media.Audio.Bitstream.DTS-HD", IsSupported(hr));

  // Older bitstream formats run at lower sampling rates.
  wfext.Format.nSamplesPerSec = 48000;
  wfext.Format.nAvgBytesPerSec = 192000;

  // Test AC3.
  wfext.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
  hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfext.Format,
                                 nullptr);
  base::UmaHistogramBoolean("Media.Audio.Bitstream.AC3", IsSupported(hr));

  // Test DTS.
  wfext.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
  hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfext.Format,
                                 nullptr);
  base::UmaHistogramBoolean("Media.Audio.Bitstream.DTS", IsSupported(hr));
}

AudioManagerWin::AudioManagerWin(std::unique_ptr<AudioThread> audio_thread,
                                 AudioLogFactory* audio_log_factory)
    : AudioManagerBase(std::move(audio_thread), audio_log_factory) {
  // |CoreAudioUtil::IsSupported()| uses static variables to avoid doing
  // multiple initializations.  This is however not thread safe.
  // So, here we call it explicitly before we kick off the audio thread
  // or do any other work.
  CoreAudioUtil::IsSupported();

  SetMaxOutputStreamsAllowed(kMaxOutputStreams);

  // WARNING: This may be executed on the UI loop, do not add any code here
  // which loads libraries or attempts to call out into the OS.  Instead add
  // such code to the InitializeOnAudioThread() method below.

  // In case we are already on the audio thread (i.e. when running out of
  // process audio), don't post.
  if (GetTaskRunner()->BelongsToCurrentThread()) {
    this->InitializeOnAudioThread();
    return;
  }

  // Task must be posted last to avoid races from handing out "this" to the
  // audio thread. Unretained is safe since we join the audio thread before
  // destructing |this|.
  GetTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&AudioManagerWin::InitializeOnAudioThread,
                                base::Unretained(this)));
}

AudioManagerWin::~AudioManagerWin() = default;

void AudioManagerWin::ShutdownOnAudioThread() {
  AudioManagerBase::ShutdownOnAudioThread();

  // Destroy AudioDeviceListenerWin instance on the audio thread because it
  // expects to be constructed and destroyed on the same thread.
  output_device_listener_.reset();
}

bool AudioManagerWin::HasAudioOutputDevices() {
  return (::waveOutGetNumDevs() != 0);
}

bool AudioManagerWin::HasAudioInputDevices() {
  return (::waveInGetNumDevs() != 0);
}

void AudioManagerWin::InitializeOnAudioThread() {
  DCHECK(GetTaskRunner()->BelongsToCurrentThread());

  // Delay metrics recording to avoid any issues at startup.
  GetTaskRunner()->PostDelayedTask(FROM_HERE,
                                   base::BindOnce(&LogBitstreamOutputSupport),
                                   base::TimeDelta::FromSeconds(15));

  // AudioDeviceListenerWin must be initialized on a COM thread.
  output_device_listener_.reset(new AudioDeviceListenerWin(BindToCurrentLoop(
      base::Bind(&AudioManagerWin::NotifyAllOutputDeviceChangeListeners,
                 base::Unretained(this)))));
}

void AudioManagerWin::GetAudioDeviceNamesImpl(bool input,
                                              AudioDeviceNames* device_names) {
  DCHECK(device_names->empty());
  // Enumerate all active audio-endpoint capture devices.
  if (input)
    GetInputDeviceNamesWin(device_names);
  else
    GetOutputDeviceNamesWin(device_names);

  if (!device_names->empty()) {
    device_names->push_front(AudioDeviceName::CreateCommunications());

    // Always add default device parameters as first element.
    device_names->push_front(AudioDeviceName::CreateDefault());
  }
}

void AudioManagerWin::GetAudioInputDeviceNames(AudioDeviceNames* device_names) {
  GetAudioDeviceNamesImpl(true, device_names);
}

void AudioManagerWin::GetAudioOutputDeviceNames(
    AudioDeviceNames* device_names) {
  GetAudioDeviceNamesImpl(false, device_names);
}

AudioParameters AudioManagerWin::GetInputStreamParameters(
    const std::string& device_id) {
  AudioParameters parameters;
  HRESULT hr =
      CoreAudioUtil::GetPreferredAudioParameters(device_id, false, &parameters);

  if (FAILED(hr) || !parameters.IsValid()) {
    LOG(WARNING) << "Unable to get preferred audio params for " << device_id
                 << " 0x" << std::hex << hr;
    // TODO(tommi): We appear to have callers to GetInputStreamParameters that
    // rely on getting valid audio parameters returned for an invalid or
    // unavailable device. We should track down those code paths (it is likely
    // that they actually don't need a real device but depend on the audio
    // code path somehow for a configuration - e.g. tab capture).
    parameters =
        AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
                        CHANNEL_LAYOUT_STEREO, 48000, kFallbackBufferSize);
  }

  int user_buffer_size = GetUserBufferSize();
  if (user_buffer_size)
    parameters.set_frames_per_buffer(user_buffer_size);

  return parameters;
}

std::string AudioManagerWin::GetAssociatedOutputDeviceID(
    const std::string& input_device_id) {
  return CoreAudioUtil::GetMatchingOutputDeviceID(input_device_id);
}

const char* AudioManagerWin::GetName() {
  return "Windows";
}

// Factory for the implementations of AudioOutputStream for AUDIO_PCM_LINEAR
// mode.
// - PCMWaveOutAudioOutputStream: Based on the waveOut API.
AudioOutputStream* AudioManagerWin::MakeLinearOutputStream(
    const AudioParameters& params,
    const LogCallback& log_callback) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
  if (params.channels() > kWinMaxChannels)
    return NULL;

  return new PCMWaveOutAudioOutputStream(this, params, NumberOfWaveOutBuffers(),
                                         WAVE_MAPPER);
}

// Factory for the implementations of AudioOutputStream for
// AUDIO_PCM_LOW_LATENCY mode. Two implementations should suffice most
// windows user's needs.
// - PCMWaveOutAudioOutputStream: Based on the waveOut API.
// - WASAPIAudioOutputStream: Based on Core Audio (WASAPI) API.
AudioOutputStream* AudioManagerWin::MakeLowLatencyOutputStream(
    const AudioParameters& params,
    const std::string& device_id,
    const LogCallback& log_callback) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
  if (params.channels() > kWinMaxChannels)
    return NULL;

  // Pass an empty string to indicate that we want the default device
  // since we consistently only check for an empty string in
  // WASAPIAudioOutputStream.
  bool communications =
      device_id == AudioDeviceDescription::kCommunicationsDeviceId;
  return new WASAPIAudioOutputStream(
      this,
      communications || device_id == AudioDeviceDescription::kDefaultDeviceId
          ? std::string()
          : device_id,
      params, communications ? eCommunications : eConsole);
}

// Factory for the implementations of AudioInputStream for AUDIO_PCM_LINEAR
// mode.
AudioInputStream* AudioManagerWin::MakeLinearInputStream(
    const AudioParameters& params,
    const std::string& device_id,
    const LogCallback& log_callback) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
  return MakeLowLatencyInputStream(params, device_id, log_callback);
}

// Factory for the implementations of AudioInputStream for
// AUDIO_PCM_LOW_LATENCY mode.
AudioInputStream* AudioManagerWin::MakeLowLatencyInputStream(
    const AudioParameters& params,
    const std::string& device_id,
    const LogCallback& log_callback) {
  // Used for both AUDIO_PCM_LOW_LATENCY and AUDIO_PCM_LINEAR.
  DVLOG(1) << "MakeLowLatencyInputStream: " << device_id;
  return new WASAPIAudioInputStream(this, params, device_id, log_callback);
}

std::string AudioManagerWin::GetDefaultInputDeviceID() {
  return CoreAudioUtil::GetDefaultInputDeviceID();
}

std::string AudioManagerWin::GetDefaultOutputDeviceID() {
  return CoreAudioUtil::GetDefaultOutputDeviceID();
}

std::string AudioManagerWin::GetCommunicationsInputDeviceID() {
  return CoreAudioUtil::GetCommunicationsInputDeviceID();
}

std::string AudioManagerWin::GetCommunicationsOutputDeviceID() {
  return CoreAudioUtil::GetCommunicationsOutputDeviceID();
}

AudioParameters AudioManagerWin::GetPreferredOutputStreamParameters(
    const std::string& output_device_id,
    const AudioParameters& input_params) {
  const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
  int sample_rate = 48000;
  int buffer_size = kFallbackBufferSize;
  int effects = AudioParameters::NO_EFFECTS;
  int min_buffer_size = 0;
  int max_buffer_size = 0;

  // TODO(henrika): Remove kEnableExclusiveAudio and related code. It doesn't
  // look like it's used.
  if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
    // TODO(rtoy): tune these values for best possible WebAudio
    // performance. WebRTC works well at 48kHz and a buffer size of 480
    // samples will be used for this case. Note that exclusive mode is
    // experimental. This sample rate will be combined with a buffer size of
    // 256 samples, which corresponds to an output delay of ~5.33ms.
    sample_rate = 48000;
    buffer_size = 256;
    if (input_params.IsValid())
      channel_layout = input_params.channel_layout();
  } else {
    AudioParameters params;
    HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(
        output_device_id.empty() ? GetDefaultOutputDeviceID()
                                 : output_device_id,
        true, &params);
    if (FAILED(hr)) {
      // This can happen when CoreAudio isn't supported or available
      // (e.g. certain installations of Windows Server 2008 R2).
      // Instead of returning the input_params, we'll return invalid
      // AudioParameters to make sure that an attempt to create this output
      // stream, won't succeed. This behavior is also consistent with
      // GetInputStreamParameters.
      DLOG(ERROR) << "GetPreferredAudioParameters failed: " << std::hex << hr;
      return AudioParameters();
    }

    DCHECK(params.IsValid());

    buffer_size = params.frames_per_buffer();
    channel_layout = params.channel_layout();
    sample_rate = params.sample_rate();
    effects = params.effects();

    AudioParameters::HardwareCapabilities hardware_capabilities =
        params.hardware_capabilities().value_or(
            AudioParameters::HardwareCapabilities());
    min_buffer_size = hardware_capabilities.min_frames_per_buffer;
    max_buffer_size = hardware_capabilities.max_frames_per_buffer;
  }

  if (input_params.IsValid()) {
    // If the user has enabled checking supported channel layouts or we don't
    // have a valid channel layout yet, try to use the input layout.  See bugs
    // http://crbug.com/259165 and http://crbug.com/311906 for more details.
    if (cmd_line->HasSwitch(switches::kTrySupportedChannelLayouts) ||
        channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
      // Check if it is possible to open up at the specified input channel
      // layout but avoid checking if the specified layout is the same as the
      // hardware (preferred) layout. We do this extra check to avoid the
      // CoreAudioUtil::IsChannelLayoutSupported() overhead in most cases.
      if (input_params.channel_layout() != channel_layout) {
        // TODO(henrika): Internally, IsChannelLayoutSupported does many of the
        // operations that have already been done such as opening up a client
        // and fetching the WAVEFORMATPCMEX format.  Ideally we should only do
        // that once.  Then here, we can check the layout from the data we
        // already hold.
        if (CoreAudioUtil::IsChannelLayoutSupported(
                output_device_id, eRender, eConsole,
                input_params.channel_layout())) {
          // Open up using the same channel layout as the source if it is
          // supported by the hardware.
          channel_layout = input_params.channel_layout();
          DVLOG(1) << "Hardware channel layout is not used; using same layout"
                   << " as the source instead (" << channel_layout << ")";
        }
      }
    }

    effects |= input_params.effects();

    // Allow non-default buffer sizes if we have a valid min and max.
    if (min_buffer_size > 0 && max_buffer_size > 0) {
      buffer_size =
          std::min(max_buffer_size,
                   std::max(input_params.frames_per_buffer(), min_buffer_size));
    }
  }

  int user_buffer_size = GetUserBufferSize();
  if (user_buffer_size)
    buffer_size = user_buffer_size;

  AudioParameters params(
      AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, sample_rate,
      buffer_size,
      AudioParameters::HardwareCapabilities(min_buffer_size, max_buffer_size));
  params.set_effects(effects);
  DCHECK(params.IsValid());
  return params;
}

// static
std::unique_ptr<AudioManager> CreateAudioManager(
    std::unique_ptr<AudioThread> audio_thread,
    AudioLogFactory* audio_log_factory) {
  return std::make_unique<AudioManagerWin>(std::move(audio_thread),
                                           audio_log_factory);
}

}  // namespace media