blob: 4ef4f6551f6646ff89e89870f731a903a70a22e1 [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.
#include "media/audio/mac/audio_low_latency_input_mac.h"
#include <CoreServices/CoreServices.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/time/time.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/audio_bus.h"
#include "media/base/data_buffer.h"
namespace media {
// Number of blocks of buffers used in the |fifo_|.
const int kNumberOfBlocksBufferInFifo = 2;
// Max length of sequence of TooManyFramesToProcessError errors.
// The stream will be stopped as soon as this time limit is passed.
const int kMaxErrorTimeoutInSeconds = 1;
// A one-shot timer is created and started in Start() and it triggers
// CheckInputStartupSuccess() after this amount of time. UMA stats marked
// Media.Audio.InputStartupSuccessMac is then updated where true is added
// if input callbacks have started, and false otherwise.
const int kInputCallbackStartTimeoutInSeconds = 5;
static std::ostream& operator<<(std::ostream& os,
const AudioStreamBasicDescription& format) {
os << "sample rate : " << format.mSampleRate << std::endl
<< "format ID : " << format.mFormatID << std::endl
<< "format flags : " << format.mFormatFlags << std::endl
<< "bytes per packet : " << format.mBytesPerPacket << std::endl
<< "frames per packet : " << format.mFramesPerPacket << std::endl
<< "bytes per frame : " << format.mBytesPerFrame << std::endl
<< "channels per frame: " << format.mChannelsPerFrame << std::endl
<< "bits per channel : " << format.mBitsPerChannel;
return os;
}
// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
// for more details and background regarding this implementation.
AUAudioInputStream::AUAudioInputStream(AudioManagerMac* manager,
const AudioParameters& input_params,
AudioDeviceID audio_device_id)
: manager_(manager),
number_of_frames_(input_params.frames_per_buffer()),
sink_(NULL),
audio_unit_(0),
input_device_id_(audio_device_id),
started_(false),
hardware_latency_frames_(0),
number_of_channels_in_frame_(0),
fifo_(input_params.channels(),
number_of_frames_,
kNumberOfBlocksBufferInFifo),
input_callback_is_active_(false),
start_was_deferred_(false),
buffer_size_was_changed_(false) {
DCHECK(manager_);
// Set up the desired (output) format specified by the client.
format_.mSampleRate = input_params.sample_rate();
format_.mFormatID = kAudioFormatLinearPCM;
format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
kLinearPCMFormatFlagIsSignedInteger;
format_.mBitsPerChannel = input_params.bits_per_sample();
format_.mChannelsPerFrame = input_params.channels();
format_.mFramesPerPacket = 1; // uncompressed audio
format_.mBytesPerPacket = (format_.mBitsPerChannel *
input_params.channels()) / 8;
format_.mBytesPerFrame = format_.mBytesPerPacket;
format_.mReserved = 0;
DVLOG(1) << "Desired ouput format: " << format_;
// Derive size (in bytes) of the buffers that we will render to.
UInt32 data_byte_size = number_of_frames_ * format_.mBytesPerFrame;
DVLOG(1) << "Size of data buffer in bytes : " << data_byte_size;
// Allocate AudioBuffers to be used as storage for the received audio.
// The AudioBufferList structure works as a placeholder for the
// AudioBuffer structure, which holds a pointer to the actual data buffer.
audio_data_buffer_.reset(new uint8[data_byte_size]);
audio_buffer_list_.mNumberBuffers = 1;
AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
audio_buffer->mNumberChannels = input_params.channels();
audio_buffer->mDataByteSize = data_byte_size;
audio_buffer->mData = audio_data_buffer_.get();
}
AUAudioInputStream::~AUAudioInputStream() {}
// Obtain and open the AUHAL AudioOutputUnit for recording.
bool AUAudioInputStream::Open() {
DCHECK(thread_checker_.CalledOnValidThread());
// Verify that we are not already opened.
if (audio_unit_)
return false;
// Verify that we have a valid device.
if (input_device_id_ == kAudioObjectUnknown) {
NOTREACHED() << "Device ID is unknown";
return false;
}
// Start by obtaining an AudioOuputUnit using an AUHAL component description.
// Description for the Audio Unit we want to use (AUHAL in this case).
AudioComponentDescription desc = {
kAudioUnitType_Output,
kAudioUnitSubType_HALOutput,
kAudioUnitManufacturer_Apple,
0,
0
};
AudioComponent comp = AudioComponentFindNext(0, &desc);
DCHECK(comp);
// Get access to the service provided by the specified Audio Unit.
OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
if (result) {
HandleError(result);
return false;
}
// Enable IO on the input scope of the Audio Unit.
// After creating the AUHAL object, we must enable IO on the input scope
// of the Audio Unit to obtain the device input. Input must be explicitly
// enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1
// of the AUHAL. Beacause the AUHAL can be used for both input and output,
// we must also disable IO on the output scope.
UInt32 enableIO = 1;
// Enable input on the AUHAL.
result = AudioUnitSetProperty(audio_unit_,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
1, // input element 1
&enableIO, // enable
sizeof(enableIO));
if (result != noErr) {
HandleError(result);
return false;
}
// Disable output on the AUHAL.
enableIO = 0;
result = AudioUnitSetProperty(audio_unit_,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
0, // output element 0
&enableIO, // disable
sizeof(enableIO));
if (result != noErr) {
HandleError(result);
return false;
}
// Next, set the audio device to be the Audio Unit's current device.
// Note that, devices can only be set to the AUHAL after enabling IO.
result = AudioUnitSetProperty(audio_unit_,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0,
&input_device_id_,
sizeof(input_device_id_));
if (result != noErr) {
HandleError(result);
return false;
}
// Set up the the desired (output) format.
// For obtaining input from a device, the device format is always expressed
// on the output scope of the AUHAL's Element 1.
result = AudioUnitSetProperty(audio_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
1,
&format_,
sizeof(format_));
if (result != noErr) {
HandleError(result);
return false;
}
if (!manager_->MaybeChangeBufferSize(input_device_id_, audio_unit_, 1,
number_of_frames_,
&buffer_size_was_changed_)) {
return false;
}
DLOG_IF(WARNING, buffer_size_was_changed_) << "IO buffer size was changed to "
<< number_of_frames_;
// Register the input procedure for the AUHAL.
// This procedure will be called when the AUHAL has received new data
// from the input device.
AURenderCallbackStruct callback;
callback.inputProc = InputProc;
callback.inputProcRefCon = this;
result = AudioUnitSetProperty(audio_unit_,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
0,
&callback,
sizeof(callback));
if (result != noErr) {
HandleError(result);
return false;
}
// Finally, initialize the audio unit and ensure that it is ready to render.
// Allocates memory according to the maximum number of audio frames
// it can produce in response to a single render call.
result = AudioUnitInitialize(audio_unit_);
if (result != noErr) {
HandleError(result);
return false;
}
// The hardware latency is fixed and will not change during the call.
hardware_latency_frames_ = GetHardwareLatency();
// The master channel is 0, Left and right are channels 1 and 2.
// And the master channel is not counted in |number_of_channels_in_frame_|.
number_of_channels_in_frame_ = GetNumberOfChannelsFromStream();
return true;
}
void AUAudioInputStream::Start(AudioInputCallback* callback) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(callback);
DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully";
if (started_ || !audio_unit_)
return;
// Check if we should defer Start() for http://crbug.com/160920.
if (manager_->ShouldDeferStreamStart()) {
start_was_deferred_ = true;
// Use a cancellable closure so that if Stop() is called before Start()
// actually runs, we can cancel the pending start.
deferred_start_cb_.Reset(base::Bind(
&AUAudioInputStream::Start, base::Unretained(this), callback));
manager_->GetTaskRunner()->PostDelayedTask(
FROM_HERE,
deferred_start_cb_.callback(),
base::TimeDelta::FromSeconds(
AudioManagerMac::kStartDelayInSecsForPowerEvents));
return;
}
sink_ = callback;
last_success_time_ = base::TimeTicks::Now();
StartAgc();
OSStatus result = AudioOutputUnitStart(audio_unit_);
if (result == noErr) {
started_ = true;
// For UMA stat purposes, start a one-shot timer which detects when input
// callbacks starts indicating if input audio recording works as intended.
// CheckInputStartupSuccess() will check if |input_callback_is_active_| is
// true when the timer expires. This timer delay is currently set to
// 5 seconds to avoid false alarms.
input_callback_timer_.reset(new base::OneShotTimer());
input_callback_timer_->Start(
FROM_HERE,
base::TimeDelta::FromSeconds(kInputCallbackStartTimeoutInSeconds), this,
&AUAudioInputStream::CheckInputStartupSuccess);
}
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "Failed to start acquiring data";
}
void AUAudioInputStream::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!started_)
return;
StopAgc();
input_callback_timer_.reset();
OSStatus result = AudioOutputUnitStop(audio_unit_);
DCHECK_EQ(result, noErr);
SetInputCallbackIsActive(false);
started_ = false;
sink_ = NULL;
fifo_.Clear();
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "Failed to stop acquiring data";
}
void AUAudioInputStream::Close() {
DCHECK(thread_checker_.CalledOnValidThread());
// It is valid to call Close() before calling open or Start().
// It is also valid to call Close() after Start() has been called.
if (started_) {
Stop();
}
CloseAudioUnit();
// Inform the audio manager that we have been closed. This can cause our
// destruction.
manager_->ReleaseInputStream(this);
}
double AUAudioInputStream::GetMaxVolume() {
// Verify that we have a valid device.
if (input_device_id_ == kAudioObjectUnknown) {
NOTREACHED() << "Device ID is unknown";
return 0.0;
}
// Query if any of the master, left or right channels has volume control.
for (int i = 0; i <= number_of_channels_in_frame_; ++i) {
// If the volume is settable, the valid volume range is [0.0, 1.0].
if (IsVolumeSettableOnChannel(i))
return 1.0;
}
// Volume control is not available for the audio stream.
return 0.0;
}
void AUAudioInputStream::SetVolume(double volume) {
DVLOG(1) << "SetVolume(volume=" << volume << ")";
DCHECK_GE(volume, 0.0);
DCHECK_LE(volume, 1.0);
// Verify that we have a valid device.
if (input_device_id_ == kAudioObjectUnknown) {
NOTREACHED() << "Device ID is unknown";
return;
}
Float32 volume_float32 = static_cast<Float32>(volume);
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMaster
};
// Try to set the volume for master volume channel.
if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) {
OSStatus result = AudioObjectSetPropertyData(input_device_id_,
&property_address,
0,
NULL,
sizeof(volume_float32),
&volume_float32);
if (result != noErr) {
DLOG(WARNING) << "Failed to set volume to " << volume_float32;
}
return;
}
// There is no master volume control, try to set volume for each channel.
int successful_channels = 0;
for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
property_address.mElement = static_cast<UInt32>(i);
if (IsVolumeSettableOnChannel(i)) {
OSStatus result = AudioObjectSetPropertyData(input_device_id_,
&property_address,
0,
NULL,
sizeof(volume_float32),
&volume_float32);
if (result == noErr)
++successful_channels;
}
}
DLOG_IF(WARNING, successful_channels == 0)
<< "Failed to set volume to " << volume_float32;
// Update the AGC volume level based on the last setting above. Note that,
// the volume-level resolution is not infinite and it is therefore not
// possible to assume that the volume provided as input parameter can be
// used directly. Instead, a new query to the audio hardware is required.
// This method does nothing if AGC is disabled.
UpdateAgcVolume();
}
double AUAudioInputStream::GetVolume() {
// Verify that we have a valid device.
if (input_device_id_ == kAudioObjectUnknown) {
NOTREACHED() << "Device ID is unknown";
return 0.0;
}
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMaster
};
if (AudioObjectHasProperty(input_device_id_, &property_address)) {
// The device supports master volume control, get the volume from the
// master channel.
Float32 volume_float32 = 0.0;
UInt32 size = sizeof(volume_float32);
OSStatus result = AudioObjectGetPropertyData(input_device_id_,
&property_address,
0,
NULL,
&size,
&volume_float32);
if (result == noErr)
return static_cast<double>(volume_float32);
} else {
// There is no master volume control, try to get the average volume of
// all the channels.
Float32 volume_float32 = 0.0;
int successful_channels = 0;
for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
property_address.mElement = static_cast<UInt32>(i);
if (AudioObjectHasProperty(input_device_id_, &property_address)) {
Float32 channel_volume = 0;
UInt32 size = sizeof(channel_volume);
OSStatus result = AudioObjectGetPropertyData(input_device_id_,
&property_address,
0,
NULL,
&size,
&channel_volume);
if (result == noErr) {
volume_float32 += channel_volume;
++successful_channels;
}
}
}
// Get the average volume of the channels.
if (successful_channels != 0)
return static_cast<double>(volume_float32 / successful_channels);
}
DLOG(WARNING) << "Failed to get volume";
return 0.0;
}
bool AUAudioInputStream::IsMuted() {
// Verify that we have a valid device.
DCHECK_NE(input_device_id_, kAudioObjectUnknown) << "Device ID is unknown";
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyMute,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMaster
};
if (!AudioObjectHasProperty(input_device_id_, &property_address)) {
DLOG(ERROR) << "Device does not support checking master mute state";
return false;
}
UInt32 muted = 0;
UInt32 size = sizeof(muted);
OSStatus result = AudioObjectGetPropertyData(
input_device_id_, &property_address, 0, NULL, &size, &muted);
DLOG_IF(WARNING, result != noErr) << "Failed to get mute state";
return result == noErr && muted != 0;
}
// AUHAL AudioDeviceOutput unit callback
OSStatus AUAudioInputStream::InputProc(void* user_data,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
// Verify that the correct bus is used (Input bus/Element 1)
DCHECK_EQ(bus_number, static_cast<UInt32>(1));
AUAudioInputStream* audio_input =
reinterpret_cast<AUAudioInputStream*>(user_data);
DCHECK(audio_input);
if (!audio_input)
return kAudioUnitErr_InvalidElement;
// Indicate that input callbacks have started on the internal AUHAL IO
// thread. The |input_callback_is_active_| member is read from the creating
// thread when a timer fires once and set to false in Stop() on the same
// thread. It means that this thread is the only writer of
// |input_callback_is_active_| once the tread starts and it should therefore
// be safe to modify.
audio_input->SetInputCallbackIsActive(true);
// Update the |mDataByteSize| value in the audio_buffer_list() since
// |number_of_frames| can be changed on the fly.
// |mDataByteSize| needs to be exactly mapping to |number_of_frames|,
// otherwise it will put CoreAudio into bad state and results in
// AudioUnitRender() returning -50 for the new created stream.
// We have also seen kAudioUnitErr_TooManyFramesToProcess (-10874) and
// kAudioUnitErr_CannotDoInCurrentContext (-10863) as error codes.
// See crbug/428706 for details.
UInt32 new_size = number_of_frames * audio_input->format_.mBytesPerFrame;
AudioBuffer* audio_buffer = audio_input->audio_buffer_list()->mBuffers;
if (new_size != audio_buffer->mDataByteSize) {
if (new_size > audio_buffer->mDataByteSize) {
// This can happen if the device is unpluged during recording. We
// allocate enough memory here to avoid depending on how CoreAudio
// handles it.
// See See http://www.crbug.com/434681 for one example when we can enter
// this scope.
audio_input->audio_data_buffer_.reset(new uint8[new_size]);
audio_buffer->mData = audio_input->audio_data_buffer_.get();
}
// Update the |mDataByteSize| to match |number_of_frames|.
audio_buffer->mDataByteSize = new_size;
}
// Receive audio from the AUHAL from the output scope of the Audio Unit.
OSStatus result = AudioUnitRender(audio_input->audio_unit(),
flags,
time_stamp,
bus_number,
number_of_frames,
audio_input->audio_buffer_list());
if (result) {
UMA_HISTOGRAM_SPARSE_SLOWLY("Media.AudioInputCbErrorMac", result);
OSSTATUS_DLOG(ERROR, result) << "AudioUnitRender() failed ";
if (result != kAudioUnitErr_TooManyFramesToProcess) {
audio_input->HandleError(result);
} else {
DCHECK(!audio_input->last_success_time_.is_null());
// We delay stopping the stream for kAudioUnitErr_TooManyFramesToProcess
// since it has been observed that some USB headsets can cause this error
// but only for a few initial frames at startup and then then the stream
// returns to a stable state again. See b/19524368 for details.
// Instead, we measure time since last valid audio frame and call
// HandleError() only if a too long error sequence is detected. We do
// this to avoid ending up in a non recoverable bad core audio state.
base::TimeDelta time_since_last_success =
base::TimeTicks::Now() - audio_input->last_success_time_;
if ((time_since_last_success >
base::TimeDelta::FromSeconds(kMaxErrorTimeoutInSeconds))) {
DLOG(ERROR) << "Too long sequence of TooManyFramesToProcess errors!";
audio_input->HandleError(result);
}
}
return result;
}
// Update time of successful call to AudioUnitRender().
audio_input->last_success_time_ = base::TimeTicks::Now();
// Deliver recorded data to the consumer as a callback.
return audio_input->Provide(number_of_frames,
audio_input->audio_buffer_list(),
time_stamp);
}
OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames,
AudioBufferList* io_data,
const AudioTimeStamp* time_stamp) {
// Update the capture latency.
double capture_latency_frames = GetCaptureLatency(time_stamp);
// The AGC volume level is updated once every second on a separate thread.
// Note that, |volume| is also updated each time SetVolume() is called
// through IPC by the render-side AGC.
double normalized_volume = 0.0;
GetAgcVolume(&normalized_volume);
AudioBuffer& buffer = io_data->mBuffers[0];
uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData);
uint32 capture_delay_bytes = static_cast<uint32>
((capture_latency_frames + 0.5) * format_.mBytesPerFrame);
DCHECK(audio_data);
if (!audio_data)
return kAudioUnitErr_InvalidElement;
// Dynamically increase capacity of the FIFO to handle larger buffers from
// CoreAudio. This can happen in combination with Apple Thunderbolt Displays
// when the Display Audio is used as capture source and the cable is first
// remove and then inserted again.
// See http://www.crbug.com/434681 for details.
if (static_cast<int>(number_of_frames) > fifo_.GetUnfilledFrames()) {
// Derive required increase in number of FIFO blocks. The increase is
// typically one block.
const int blocks =
static_cast<int>((number_of_frames - fifo_.GetUnfilledFrames()) /
number_of_frames_) + 1;
DLOG(WARNING) << "Increasing FIFO capacity by " << blocks << " blocks";
fifo_.IncreaseCapacity(blocks);
}
// Copy captured (and interleaved) data into FIFO.
fifo_.Push(audio_data, number_of_frames, format_.mBitsPerChannel / 8);
// Consume and deliver the data when the FIFO has a block of available data.
while (fifo_.available_blocks()) {
const AudioBus* audio_bus = fifo_.Consume();
DCHECK_EQ(audio_bus->frames(), static_cast<int>(number_of_frames_));
// Compensate the audio delay caused by the FIFO.
capture_delay_bytes += fifo_.GetAvailableFrames() * format_.mBytesPerFrame;
sink_->OnData(this, audio_bus, capture_delay_bytes, normalized_volume);
}
return noErr;
}
int AUAudioInputStream::HardwareSampleRate() {
// Determine the default input device's sample-rate.
AudioDeviceID device_id = kAudioObjectUnknown;
UInt32 info_size = sizeof(device_id);
AudioObjectPropertyAddress default_input_device_address = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&default_input_device_address,
0,
0,
&info_size,
&device_id);
if (result != noErr)
return 0.0;
Float64 nominal_sample_rate;
info_size = sizeof(nominal_sample_rate);
AudioObjectPropertyAddress nominal_sample_rate_address = {
kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
result = AudioObjectGetPropertyData(device_id,
&nominal_sample_rate_address,
0,
0,
&info_size,
&nominal_sample_rate);
if (result != noErr)
return 0.0;
return static_cast<int>(nominal_sample_rate);
}
double AUAudioInputStream::GetHardwareLatency() {
if (!audio_unit_ || input_device_id_ == kAudioObjectUnknown) {
DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
return 0.0;
}
// Get audio unit latency.
Float64 audio_unit_latency_sec = 0.0;
UInt32 size = sizeof(audio_unit_latency_sec);
OSStatus result = AudioUnitGetProperty(audio_unit_,
kAudioUnitProperty_Latency,
kAudioUnitScope_Global,
0,
&audio_unit_latency_sec,
&size);
OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
<< "Could not get audio unit latency";
// Get input audio device latency.
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyLatency,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMaster
};
UInt32 device_latency_frames = 0;
size = sizeof(device_latency_frames);
result = AudioObjectGetPropertyData(input_device_id_,
&property_address,
0,
NULL,
&size,
&device_latency_frames);
DLOG_IF(WARNING, result != noErr) << "Could not get audio device latency.";
return static_cast<double>((audio_unit_latency_sec *
format_.mSampleRate) + device_latency_frames);
}
double AUAudioInputStream::GetCaptureLatency(
const AudioTimeStamp* input_time_stamp) {
// Get the delay between between the actual recording instant and the time
// when the data packet is provided as a callback.
UInt64 capture_time_ns = AudioConvertHostTimeToNanos(
input_time_stamp->mHostTime);
UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
double delay_frames = static_cast<double>
(1e-9 * (now_ns - capture_time_ns) * format_.mSampleRate);
// Total latency is composed by the dynamic latency and the fixed
// hardware latency.
return (delay_frames + hardware_latency_frames_);
}
int AUAudioInputStream::GetNumberOfChannelsFromStream() {
// Get the stream format, to be able to read the number of channels.
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyStreamFormat,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMaster
};
AudioStreamBasicDescription stream_format;
UInt32 size = sizeof(stream_format);
OSStatus result = AudioObjectGetPropertyData(input_device_id_,
&property_address,
0,
NULL,
&size,
&stream_format);
if (result != noErr) {
DLOG(WARNING) << "Could not get stream format";
return 0;
}
return static_cast<int>(stream_format.mChannelsPerFrame);
}
void AUAudioInputStream::HandleError(OSStatus err) {
NOTREACHED() << "error " << GetMacOSStatusErrorString(err)
<< " (" << err << ")";
if (sink_)
sink_->OnError(this);
}
bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel) {
Boolean is_settable = false;
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar,
kAudioDevicePropertyScopeInput,
static_cast<UInt32>(channel)
};
OSStatus result = AudioObjectIsPropertySettable(input_device_id_,
&property_address,
&is_settable);
return (result == noErr) ? is_settable : false;
}
void AUAudioInputStream::SetInputCallbackIsActive(bool enabled) {
base::subtle::Release_Store(&input_callback_is_active_, enabled);
}
bool AUAudioInputStream::GetInputCallbackIsActive() {
return (base::subtle::Acquire_Load(&input_callback_is_active_) != false);
}
void AUAudioInputStream::CheckInputStartupSuccess() {
DCHECK(thread_checker_.CalledOnValidThread());
if (started_) {
// Check if we have called Start() and input callbacks have actually
// started in time as they should. If that is not the case, we have a
// problem and the stream is considered dead.
const bool input_callback_is_active = GetInputCallbackIsActive();
UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartupSuccessMac",
input_callback_is_active);
DVLOG(1) << "input_callback_is_active: " << input_callback_is_active;
if (!input_callback_is_active) {
// Now when we know that startup has failed for some reason, add extra
// UMA stats in an attempt to figure out the exact reason.
AddHistogramsForFailedStartup();
}
}
}
void AUAudioInputStream::CloseAudioUnit() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!audio_unit_)
return;
OSStatus result = AudioUnitUninitialize(audio_unit_);
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "AudioUnitUninitialize() failed.";
result = AudioComponentInstanceDispose(audio_unit_);
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "AudioComponentInstanceDispose() failed.";
audio_unit_ = 0;
}
void AUAudioInputStream::AddHistogramsForFailedStartup() {
DCHECK(thread_checker_.CalledOnValidThread());
UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartWasDeferredMac",
start_was_deferred_);
UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputBufferSizeWasChangedMac",
buffer_size_was_changed_);
UMA_HISTOGRAM_COUNTS_1000("Media.Audio.NumberOfOutputStreamsMac",
manager_->output_streams());
UMA_HISTOGRAM_COUNTS_1000("Media.Audio.NumberOfLowLatencyInputStreamsMac",
manager_->low_latency_input_streams());
UMA_HISTOGRAM_COUNTS_1000("Media.Audio.NumberOfBasicInputStreamsMac",
manager_->basic_input_streams());
}
} // namespace media