blob: e91610f3aaa1a8df16856f6b60e9575f7a775946 [file] [log] [blame]
// 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.
// Software adjust volume of samples, allows each audio stream its own
// volume without impacting master volume for chrome and other applications.
// Implemented as templates to allow 8, 16 and 32 bit implementations.
// 8 bit is unsigned and biased by 128.
// TODO(vrk): This file has been running pretty wild and free, and it's likely
// that a lot of the functions can be simplified and made more elegant. Revisit
// after other audio cleanup is done. (crbug.com/120319)
#include "media/audio/audio_util.h"
#include <algorithm>
#include <limits>
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/string_number_conversions.h"
#include "base/time.h"
#include "media/audio/audio_parameters.h"
#include "media/base/audio_bus.h"
#include "media/base/media_switches.h"
#if defined(OS_MACOSX)
#include "media/audio/mac/audio_low_latency_input_mac.h"
#include "media/audio/mac/audio_low_latency_output_mac.h"
#elif defined(OS_WIN)
#include "base/win/windows_version.h"
#include "media/audio/audio_manager_base.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/base/limits.h"
#endif
namespace media {
// Returns user buffer size as specified on the command line or 0 if no buffer
// size has been specified.
static int GetUserBufferSize() {
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
int buffer_size = 0;
std::string buffer_size_str(cmd_line->GetSwitchValueASCII(
switches::kAudioBufferSize));
if (base::StringToInt(buffer_size_str, &buffer_size) && buffer_size > 0) {
return buffer_size;
}
return 0;
}
// TODO(fbarchard): Convert to intrinsics for better efficiency.
template<class Fixed>
static int ScaleChannel(int channel, int volume) {
return static_cast<int>((static_cast<Fixed>(channel) * volume) >> 16);
}
template<class Format, class Fixed, int bias>
static void AdjustVolume(Format* buf_out,
int sample_count,
int fixed_volume) {
for (int i = 0; i < sample_count; ++i) {
buf_out[i] = static_cast<Format>(ScaleChannel<Fixed>(buf_out[i] - bias,
fixed_volume) + bias);
}
}
template<class Fixed, int min_value, int max_value>
static int AddSaturated(int val, int adder) {
Fixed sum = static_cast<Fixed>(val) + static_cast<Fixed>(adder);
if (sum > max_value)
return max_value;
if (sum < min_value)
return min_value;
return static_cast<int>(sum);
}
// AdjustVolume() does an in place audio sample change.
bool AdjustVolume(void* buf,
size_t buflen,
int channels,
int bytes_per_sample,
float volume) {
DCHECK(buf);
if (volume < 0.0f || volume > 1.0f)
return false;
if (volume == 1.0f) {
return true;
} else if (volume == 0.0f) {
memset(buf, 0, buflen);
return true;
}
if (channels > 0 && channels <= 8 && bytes_per_sample > 0) {
int sample_count = buflen / bytes_per_sample;
const int fixed_volume = static_cast<int>(volume * 65536);
if (bytes_per_sample == 1) {
AdjustVolume<uint8, int32, 128>(reinterpret_cast<uint8*>(buf),
sample_count,
fixed_volume);
return true;
} else if (bytes_per_sample == 2) {
AdjustVolume<int16, int32, 0>(reinterpret_cast<int16*>(buf),
sample_count,
fixed_volume);
return true;
} else if (bytes_per_sample == 4) {
AdjustVolume<int32, int64, 0>(reinterpret_cast<int32*>(buf),
sample_count,
fixed_volume);
return true;
}
}
return false;
}
// TODO(enal): use template specialization and size-specific intrinsics.
// Call is on the time-critical path, and by using SSE/AVX
// instructions we can speed things up by ~4-8x, more for the case
// when we have to adjust volume as well.
template<class Format, class Fixed, int min_value, int max_value, int bias>
static void MixStreams(Format* dst, Format* src, int count, float volume) {
if (volume == 0.0f)
return;
if (volume == 1.0f) {
// Most common case -- no need to adjust volume.
for (int i = 0; i < count; ++i) {
Fixed value = AddSaturated<Fixed, min_value, max_value>(dst[i] - bias,
src[i] - bias);
dst[i] = static_cast<Format>(value + bias);
}
} else {
// General case -- have to adjust volume before mixing.
const int fixed_volume = static_cast<int>(volume * 65536);
for (int i = 0; i < count; ++i) {
Fixed adjusted_src = ScaleChannel<Fixed>(src[i] - bias, fixed_volume);
Fixed value = AddSaturated<Fixed, min_value, max_value>(dst[i] - bias,
adjusted_src);
dst[i] = static_cast<Format>(value + bias);
}
}
}
void MixStreams(void* dst,
void* src,
size_t buflen,
int bytes_per_sample,
float volume) {
DCHECK(dst);
DCHECK(src);
DCHECK_GE(volume, 0.0f);
DCHECK_LE(volume, 1.0f);
switch (bytes_per_sample) {
case 1:
MixStreams<uint8, int32, kint8min, kint8max, 128>(
static_cast<uint8*>(dst),
static_cast<uint8*>(src),
buflen,
volume);
break;
case 2:
DCHECK_EQ(0u, buflen % 2);
MixStreams<int16, int32, kint16min, kint16max, 0>(
static_cast<int16*>(dst),
static_cast<int16*>(src),
buflen / 2,
volume);
break;
case 4:
DCHECK_EQ(0u, buflen % 4);
MixStreams<int32, int64, kint32min, kint32max, 0>(
static_cast<int32*>(dst),
static_cast<int32*>(src),
buflen / 4,
volume);
break;
default:
NOTREACHED() << "Illegal bytes per sample";
break;
}
}
int GetAudioHardwareSampleRate() {
#if defined(OS_MACOSX)
// Hardware sample-rate on the Mac can be configured, so we must query.
return AUAudioOutputStream::HardwareSampleRate();
#elif defined(OS_WIN)
if (!CoreAudioUtil::IsSupported()) {
// Fall back to Windows Wave implementation on Windows XP or lower
// and use 48kHz as default input sample rate.
return 48000;
}
// TODO(crogers): tune this rate 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.
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
// This sample rate will be combined with a buffer size of 256 samples
// (see GetAudioHardwareBufferSize()), which corresponds to an output
// delay of ~5.33ms.
return 48000;
}
// Hardware sample-rate on Windows can be configured, so we must query.
// TODO(henrika): improve possibility to specify an audio endpoint.
// Use the default device (same as for Wave) for now to be compatible
// or possibly remove the ERole argument completely until it is in use.
return WASAPIAudioOutputStream::HardwareSampleRate(eConsole);
#elif defined(OS_ANDROID)
return 16000;
#else
// Hardware for Linux is nearly always 48KHz.
// TODO(crogers) : return correct value in rare non-48KHz cases.
return 48000;
#endif
}
int GetAudioInputHardwareSampleRate(const std::string& device_id) {
// TODO(henrika): add support for device selection on all platforms.
// Only exists on Windows today.
#if defined(OS_MACOSX)
return AUAudioInputStream::HardwareSampleRate();
#elif defined(OS_WIN)
if (!CoreAudioUtil::IsSupported()) {
return 48000;
}
return WASAPIAudioInputStream::HardwareSampleRate(device_id);
#elif defined(OS_ANDROID)
return 16000;
#else
return 48000;
#endif
}
size_t GetAudioHardwareBufferSize() {
int user_buffer_size = GetUserBufferSize();
if (user_buffer_size)
return user_buffer_size;
// The sizes here were determined by experimentation and are roughly
// the lowest value (for low latency) that still allowed glitch-free
// audio under high loads.
//
// For Mac OS X and Windows the chromium audio backend uses a low-latency
// Core Audio API, so a low buffer size is possible. For Linux, further
// tuning may be needed.
#if defined(OS_MACOSX)
return 128;
#elif defined(OS_WIN)
// Buffer size to use when a proper size can't be determined from the system.
static const int kFallbackBufferSize = 4096;
if (!CoreAudioUtil::IsSupported()) {
// Fall back to Windows Wave implementation on Windows XP or lower
// and assume 48kHz as default sample rate.
return kFallbackBufferSize;
}
// TODO(crogers): tune this size to best possible WebAudio performance.
// WebRTC always uses 10ms for Windows and does not call this method.
// Note that exclusive mode is experimental.
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
return 256;
}
// TODO(henrika): remove when the --enable-webaudio-input flag is no longer
// utilized.
if (cmd_line->HasSwitch(switches::kEnableWebAudioInput)) {
AudioParameters params;
HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(eRender, eConsole,
&params);
return FAILED(hr) ? kFallbackBufferSize : params.frames_per_buffer();
}
// This call must be done on a COM thread configured as MTA.
// TODO(tommi): http://code.google.com/p/chromium/issues/detail?id=103835.
int mixing_sample_rate =
WASAPIAudioOutputStream::HardwareSampleRate(eConsole);
// Windows will return a sample rate of 0 when no audio output is available
// (i.e. via RemoteDesktop with remote audio disabled), but we should never
// return a buffer size of zero.
if (mixing_sample_rate == 0)
return kFallbackBufferSize;
// Use different buffer sizes depening on the sample rate . The existing
// WASAPI implementation is tuned to provide the most stable callback
// sequence using these combinations.
if (mixing_sample_rate % 11025 == 0)
// Use buffer size of ~10.15873 ms.
return (112 * (mixing_sample_rate / 11025));
if (mixing_sample_rate % 8000 == 0)
// Use buffer size of 10ms.
return (80 * (mixing_sample_rate / 8000));
// Ensure we always return a buffer size which is somewhat appropriate.
LOG(ERROR) << "Unknown sample rate " << mixing_sample_rate << " detected.";
if (mixing_sample_rate > limits::kMinSampleRate)
return (mixing_sample_rate / 100);
return kFallbackBufferSize;
#else
return 2048;
#endif
}
ChannelLayout GetAudioInputHardwareChannelLayout(const std::string& device_id) {
// TODO(henrika): add support for device selection on all platforms.
// Only exists on Windows today.
#if defined(OS_MACOSX)
return CHANNEL_LAYOUT_MONO;
#elif defined(OS_WIN)
if (!CoreAudioUtil::IsSupported()) {
// Fall back to Windows Wave implementation on Windows XP or lower and
// use stereo by default.
return CHANNEL_LAYOUT_STEREO;
}
return WASAPIAudioInputStream::HardwareChannelCount(device_id) == 1 ?
CHANNEL_LAYOUT_MONO : CHANNEL_LAYOUT_STEREO;
#else
return CHANNEL_LAYOUT_STEREO;
#endif
}
// Computes a buffer size based on the given |sample_rate|. Must be used in
// conjunction with AUDIO_PCM_LINEAR.
size_t GetHighLatencyOutputBufferSize(int sample_rate) {
int user_buffer_size = GetUserBufferSize();
if (user_buffer_size)
return user_buffer_size;
// TODO(vrk/crogers): The buffer sizes that this function computes is probably
// overly conservative. However, reducing the buffer size to 2048-8192 bytes
// caused crbug.com/108396. This computation should be revisited while making
// sure crbug.com/108396 doesn't happen again.
// The minimum number of samples in a hardware packet.
// This value is selected so that we can handle down to 5khz sample rate.
static const size_t kMinSamplesPerHardwarePacket = 1024;
// The maximum number of samples in a hardware packet.
// This value is selected so that we can handle up to 192khz sample rate.
static const size_t kMaxSamplesPerHardwarePacket = 64 * 1024;
// This constant governs the hardware audio buffer size, this value should be
// chosen carefully.
// This value is selected so that we have 8192 samples for 48khz streams.
static const size_t kMillisecondsPerHardwarePacket = 170;
// Select the number of samples that can provide at least
// |kMillisecondsPerHardwarePacket| worth of audio data.
size_t samples = kMinSamplesPerHardwarePacket;
while (samples <= kMaxSamplesPerHardwarePacket &&
samples * base::Time::kMillisecondsPerSecond <
sample_rate * kMillisecondsPerHardwarePacket) {
samples *= 2;
}
return samples;
}
#if defined(OS_WIN)
int NumberOfWaveOutBuffers() {
// Use 4 buffers for Vista, 3 for everyone else:
// - The entire Windows audio stack was rewritten for Windows Vista and wave
// out performance was degraded compared to XP.
// - The regression was fixed in Windows 7 and most configurations will work
// with 2, but some (e.g., some Sound Blasters) still need 3.
// - Some XP configurations (even multi-processor ones) also need 3.
return (base::win::GetVersion() == base::win::VERSION_VISTA) ? 4 : 3;
}
#endif
} // namespace media