blob: 2672a2701d96e2d70b9b614b9e62e4cf954663b2 [file] [log] [blame]
// Copyright 2013 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 "content/renderer/media/webrtc_audio_device_impl.h"
#include "base/bind.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/win/windows_version.h"
#include "content/renderer/media/webrtc_audio_capturer.h"
#include "content/renderer/media/webrtc_audio_renderer.h"
#include "content/renderer/render_thread_impl.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/audio_util.h"
#include "media/audio/sample_rates.h"
using media::AudioParameters;
using media::ChannelLayout;
namespace content {
namespace {
const double kMaxVolumeLevel = 255.0;
} // namespace
WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl()
: ref_count_(0),
audio_transport_callback_(NULL),
input_delay_ms_(0),
output_delay_ms_(0),
initialized_(false),
playing_(false),
recording_(false),
agc_is_enabled_(false),
microphone_volume_(0) {
DVLOG(1) << "WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl()";
}
WebRtcAudioDeviceImpl::~WebRtcAudioDeviceImpl() {
DVLOG(1) << "WebRtcAudioDeviceImpl::~WebRtcAudioDeviceImpl()";
DCHECK(thread_checker_.CalledOnValidThread());
Terminate();
}
int32_t WebRtcAudioDeviceImpl::AddRef() {
DCHECK(thread_checker_.CalledOnValidThread());
return base::subtle::Barrier_AtomicIncrement(&ref_count_, 1);
}
int32_t WebRtcAudioDeviceImpl::Release() {
DCHECK(thread_checker_.CalledOnValidThread());
int ret = base::subtle::Barrier_AtomicIncrement(&ref_count_, -1);
if (ret == 0) {
delete this;
}
return ret;
}
void WebRtcAudioDeviceImpl::CaptureData(const int16* audio_data,
int number_of_channels,
int number_of_frames,
int audio_delay_milliseconds,
double volume) {
DCHECK_LE(number_of_frames, input_buffer_size());
#if defined(OS_WIN) || defined(OS_MACOSX)
DCHECK_LE(volume, 1.0);
#elif defined(OS_LINUX) || defined(OS_OPENBSD)
// We have a special situation on Linux where the microphone volume can be
// "higher than maximum". The input volume slider in the sound preference
// allows the user to set a scaling that is higher than 100%. It means that
// even if the reported maximum levels is N, the actual microphone level can
// go up to 1.5x*N and that corresponds to a normalized |volume| of 1.5x.
DCHECK_LE(volume, 1.6);
#endif
media::AudioParameters input_audio_parameters;
int output_delay_ms = 0;
{
base::AutoLock auto_lock(lock_);
if (!recording_)
return;
// Take a copy of the input parameters while we are under a lock.
input_audio_parameters = input_audio_parameters_;
// Store the reported audio delay locally.
input_delay_ms_ = audio_delay_milliseconds;
output_delay_ms = output_delay_ms_;
DVLOG(2) << "total delay: " << input_delay_ms_ + output_delay_ms_;
// Map internal volume range of [0.0, 1.0] into [0, 255] used by the
// webrtc::VoiceEngine.
microphone_volume_ = static_cast<uint32_t>(volume * kMaxVolumeLevel);
}
const int channels = number_of_channels;
DCHECK_LE(channels, input_channels());
uint32_t new_mic_level = 0;
int samples_per_sec = input_sample_rate();
const int samples_per_10_msec = (samples_per_sec / 100);
int bytes_per_sample = input_audio_parameters.bits_per_sample() / 8;
const int bytes_per_10_msec =
channels * samples_per_10_msec * bytes_per_sample;
int accumulated_audio_samples = 0;
const uint8* audio_byte_buffer = reinterpret_cast<const uint8*>(audio_data);
// Write audio samples in blocks of 10 milliseconds to the registered
// webrtc::AudioTransport sink. Keep writing until our internal byte
// buffer is empty.
// TODO(niklase): Wire up the key press detection.
bool key_pressed = false;
while (accumulated_audio_samples < number_of_frames) {
// Deliver 10ms of recorded 16-bit linear PCM audio.
audio_transport_callback_->RecordedDataIsAvailable(
audio_byte_buffer,
samples_per_10_msec,
bytes_per_sample,
channels,
samples_per_sec,
input_delay_ms_ + output_delay_ms,
0, // TODO(henrika): |clock_drift| parameter is not utilized today.
microphone_volume_,
key_pressed,
new_mic_level);
accumulated_audio_samples += samples_per_10_msec;
audio_byte_buffer += bytes_per_10_msec;
}
// The AGC returns a non-zero microphone level if it has been decided
// that a new level should be set.
if (new_mic_level != 0) {
// Use IPC and set the new level. Note that, it will take some time
// before the new level is effective due to the IPC scheme.
// During this time, |current_mic_level| will contain "non-valid" values
// and it might reduce the AGC performance. Measurements on Windows 7 have
// shown that we might receive old volume levels for one or two callbacks.
SetMicrophoneVolume(new_mic_level);
}
}
void WebRtcAudioDeviceImpl::SetCaptureFormat(
const media::AudioParameters& params) {
DVLOG(1) << "WebRtcAudioDeviceImpl::SetCaptureFormat()";
DCHECK(thread_checker_.CalledOnValidThread());
base::AutoLock auto_lock(lock_);
input_audio_parameters_ = params;
}
void WebRtcAudioDeviceImpl::RenderData(uint8* audio_data,
int number_of_channels,
int number_of_frames,
int audio_delay_milliseconds) {
DCHECK_LE(number_of_frames, output_buffer_size());
{
base::AutoLock auto_lock(lock_);
// Store the reported audio delay locally.
output_delay_ms_ = audio_delay_milliseconds;
}
const int channels = number_of_channels;
DCHECK_LE(channels, output_channels());
int samples_per_sec = output_sample_rate();
int samples_per_10_msec = (samples_per_sec / 100);
int bytes_per_sample = output_audio_parameters_.bits_per_sample() / 8;
const int bytes_per_10_msec =
channels * samples_per_10_msec * bytes_per_sample;
uint32_t num_audio_samples = 0;
int accumulated_audio_samples = 0;
// Get audio samples in blocks of 10 milliseconds from the registered
// webrtc::AudioTransport source. Keep reading until our internal buffer
// is full.
while (accumulated_audio_samples < number_of_frames) {
// Get 10ms and append output to temporary byte buffer.
audio_transport_callback_->NeedMorePlayData(samples_per_10_msec,
bytes_per_sample,
channels,
samples_per_sec,
audio_data,
num_audio_samples);
accumulated_audio_samples += num_audio_samples;
audio_data += bytes_per_10_msec;
}
}
void WebRtcAudioDeviceImpl::SetRenderFormat(const AudioParameters& params) {
DCHECK(thread_checker_.CalledOnValidThread());
output_audio_parameters_ = params;
}
void WebRtcAudioDeviceImpl::RemoveAudioRenderer(WebRtcAudioRenderer* renderer) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(renderer, renderer_);
base::AutoLock auto_lock(lock_);
renderer_ = NULL;
playing_ = false;
}
int32_t WebRtcAudioDeviceImpl::RegisterAudioCallback(
webrtc::AudioTransport* audio_callback) {
DVLOG(1) << "WebRtcAudioDeviceImpl::RegisterAudioCallback()";
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(audio_transport_callback_ == NULL, audio_callback != NULL);
audio_transport_callback_ = audio_callback;
return 0;
}
int32_t WebRtcAudioDeviceImpl::Init() {
DVLOG(1) << "WebRtcAudioDeviceImpl::Init()";
DCHECK(thread_checker_.CalledOnValidThread());
if (initialized_)
return 0;
DCHECK(!capturer_.get());
capturer_ = WebRtcAudioCapturer::CreateCapturer();
// Add itself as an audio track to the |capturer_|. This is because WebRTC
// supports only one ADM but multiple audio tracks, so the ADM can't be the
// sink of certain audio track now.
// TODO(xians): Register the ADM as the sink of the audio track if WebRTC
// supports one ADM for each audio track. See http://crbug/247027.
capturer_->SetDefaultSink(this);
// We need to return a success to continue the initialization of WebRtc VoE
// because failure on the capturer_ initialization should not prevent WebRTC
// from working. See issue http://crbug.com/144421 for details.
initialized_ = true;
return 0;
}
int32_t WebRtcAudioDeviceImpl::Terminate() {
DVLOG(1) << "WebRtcAudioDeviceImpl::Terminate()";
DCHECK(thread_checker_.CalledOnValidThread());
// Calling Terminate() multiple times in a row is OK.
if (!initialized_)
return 0;
StopRecording();
StopPlayout();
// It is necessary to stop the |renderer_| before going away.
if (renderer_.get()) {
// Grab a local reference while we call Stop(), which will trigger a call to
// RemoveAudioRenderer that clears our reference to the audio renderer.
scoped_refptr<WebRtcAudioRenderer> local_renderer(renderer_);
local_renderer->Stop();
DCHECK(!renderer_.get());
}
if (capturer_.get()) {
// |capturer_| is stopped by the media stream, so do not need to
// call Stop() here.
capturer_->SetDefaultSink(NULL);
capturer_ = NULL;
}
initialized_ = false;
return 0;
}
bool WebRtcAudioDeviceImpl::Initialized() const {
return initialized_;
}
int32_t WebRtcAudioDeviceImpl::PlayoutIsAvailable(bool* available) {
*available = initialized_;
return 0;
}
bool WebRtcAudioDeviceImpl::PlayoutIsInitialized() const {
return initialized_;
}
int32_t WebRtcAudioDeviceImpl::RecordingIsAvailable(bool* available) {
*available = (capturer_.get() != NULL);
return 0;
}
bool WebRtcAudioDeviceImpl::RecordingIsInitialized() const {
DVLOG(1) << "WebRtcAudioDeviceImpl::RecordingIsInitialized()";
DCHECK(thread_checker_.CalledOnValidThread());
return (capturer_.get() != NULL);
}
int32_t WebRtcAudioDeviceImpl::StartPlayout() {
DVLOG(1) << "WebRtcAudioDeviceImpl::StartPlayout()";
LOG_IF(ERROR, !audio_transport_callback_) << "Audio transport is missing";
{
base::AutoLock auto_lock(lock_);
if (!audio_transport_callback_)
return 0;
}
if (playing_) {
// webrtc::VoiceEngine assumes that it is OK to call Start() twice and
// that the call is ignored the second time.
return 0;
}
playing_ = true;
start_render_time_ = base::Time::Now();
return 0;
}
int32_t WebRtcAudioDeviceImpl::StopPlayout() {
DVLOG(1) << "WebRtcAudioDeviceImpl::StopPlayout()";
if (!playing_) {
// webrtc::VoiceEngine assumes that it is OK to call Stop() just in case.
return 0;
}
// Add histogram data to be uploaded as part of an UMA logging event.
// This histogram keeps track of total playout times.
if (!start_render_time_.is_null()) {
base::TimeDelta render_time = base::Time::Now() - start_render_time_;
UMA_HISTOGRAM_LONG_TIMES("WebRTC.AudioRenderTime", render_time);
}
playing_ = false;
return 0;
}
bool WebRtcAudioDeviceImpl::Playing() const {
return playing_;
}
int32_t WebRtcAudioDeviceImpl::StartRecording() {
DVLOG(1) << "WebRtcAudioDeviceImpl::StartRecording()";
DCHECK(initialized_);
LOG_IF(ERROR, !audio_transport_callback_) << "Audio transport is missing";
if (!audio_transport_callback_) {
return -1;
}
{
base::AutoLock auto_lock(lock_);
if (recording_)
return 0;
recording_ = true;
}
start_capture_time_ = base::Time::Now();
return 0;
}
int32_t WebRtcAudioDeviceImpl::StopRecording() {
DVLOG(1) << "WebRtcAudioDeviceImpl::StopRecording()";
{
base::AutoLock auto_lock(lock_);
if (!recording_)
return 0;
recording_ = false;
}
// Add histogram data to be uploaded as part of an UMA logging event.
// This histogram keeps track of total recording times.
if (!start_capture_time_.is_null()) {
base::TimeDelta capture_time = base::Time::Now() - start_capture_time_;
UMA_HISTOGRAM_LONG_TIMES("WebRTC.AudioCaptureTime", capture_time);
}
return 0;
}
bool WebRtcAudioDeviceImpl::Recording() const {
base::AutoLock auto_lock(lock_);
return recording_;
}
int32_t WebRtcAudioDeviceImpl::SetAGC(bool enable) {
DVLOG(1) << "WebRtcAudioDeviceImpl::SetAGC(enable=" << enable << ")";
DCHECK(initialized_);
// Return early if we are not changing the AGC state.
if (enable == agc_is_enabled_)
return 0;
// The current implementation does not support changing the AGC state while
// recording. Using this approach simplifies the design and it is also
// inline with the latest WebRTC standard.
if (!capturer_.get() || capturer_->is_recording())
return -1;
capturer_->SetAutomaticGainControl(enable);
agc_is_enabled_ = enable;
return 0;
}
bool WebRtcAudioDeviceImpl::AGC() const {
DVLOG(1) << "WebRtcAudioDeviceImpl::AGC()";
DCHECK(thread_checker_.CalledOnValidThread());
// To reduce the usage of IPC messages, an internal AGC state is used.
// TODO(henrika): investigate if there is a need for a "deeper" getter.
return agc_is_enabled_;
}
int32_t WebRtcAudioDeviceImpl::SetMicrophoneVolume(uint32_t volume) {
DVLOG(1) << "WebRtcAudioDeviceImpl::SetMicrophoneVolume(" << volume << ")";
DCHECK(initialized_);
if (!capturer_.get())
return -1;
if (volume > kMaxVolumeLevel)
return -1;
// WebRTC uses a range of [0, 255] to represent the level of the microphone
// volume. The IPC channel between the renderer and browser process works
// with doubles in the [0.0, 1.0] range and we have to compensate for that.
double normalized_volume = static_cast<double>(volume) / kMaxVolumeLevel;
capturer_->SetVolume(normalized_volume);
return 0;
}
// TODO(henrika): sort out calling thread once we start using this API.
int32_t WebRtcAudioDeviceImpl::MicrophoneVolume(uint32_t* volume) const {
DVLOG(1) << "WebRtcAudioDeviceImpl::MicrophoneVolume()";
// The microphone level is fed to this class using the Capture() callback
// and cached in the same method, i.e. we don't ask the native audio layer
// for the actual micropone level here.
DCHECK(initialized_);
if (!capturer_.get())
return -1;
base::AutoLock auto_lock(lock_);
*volume = microphone_volume_;
return 0;
}
int32_t WebRtcAudioDeviceImpl::MaxMicrophoneVolume(uint32_t* max_volume) const {
*max_volume = kMaxVolumeLevel;
return 0;
}
int32_t WebRtcAudioDeviceImpl::MinMicrophoneVolume(uint32_t* min_volume) const {
*min_volume = 0;
return 0;
}
int32_t WebRtcAudioDeviceImpl::StereoPlayoutIsAvailable(bool* available) const {
DCHECK(initialized_);
*available = (output_channels() == 2);
return 0;
}
int32_t WebRtcAudioDeviceImpl::StereoRecordingIsAvailable(
bool* available) const {
DCHECK(initialized_);
if (!capturer_.get())
return -1;
*available = (input_channels() == 2);
return 0;
}
int32_t WebRtcAudioDeviceImpl::PlayoutDelay(uint16_t* delay_ms) const {
base::AutoLock auto_lock(lock_);
*delay_ms = static_cast<uint16_t>(output_delay_ms_);
return 0;
}
int32_t WebRtcAudioDeviceImpl::RecordingDelay(uint16_t* delay_ms) const {
base::AutoLock auto_lock(lock_);
*delay_ms = static_cast<uint16_t>(input_delay_ms_);
return 0;
}
int32_t WebRtcAudioDeviceImpl::RecordingSampleRate(
uint32_t* samples_per_sec) const {
*samples_per_sec = static_cast<uint32_t>(input_sample_rate());
return 0;
}
int32_t WebRtcAudioDeviceImpl::PlayoutSampleRate(
uint32_t* samples_per_sec) const {
*samples_per_sec = static_cast<uint32_t>(output_sample_rate());
return 0;
}
bool WebRtcAudioDeviceImpl::SetAudioRenderer(WebRtcAudioRenderer* renderer) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(renderer);
base::AutoLock auto_lock(lock_);
if (renderer_.get())
return false;
if (!renderer->Initialize(this))
return false;
renderer_ = renderer;
return true;
}
} // namespace content