blob: a9bc88e2bd3be7205dab6062ed63fa44d2db0b7c [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_synchronized_mac.h"
#include <CoreServices/CoreServices.h>
#include <algorithm>
#include "base/basictypes.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/channel_mixer.h"
namespace media {
static const int kHardwareBufferSize = 128;
static const int kFifoSize = 16384;
// TODO(crogers): handle the non-stereo case.
static const int kChannels = 2;
// This value was determined empirically for minimum latency while still
// guarding against FIFO under-runs.
static const int kBaseTargetFifoFrames = 256 + 64;
// If the input and output sample-rate don't match, then we need to maintain
// an additional safety margin due to the callback timing jitter and the
// varispeed buffering. This value was empirically tuned.
static const int kAdditionalTargetFifoFrames = 128;
static void ZeroBufferList(AudioBufferList* buffer_list) {
for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i)
memset(buffer_list->mBuffers[i].mData,
0,
buffer_list->mBuffers[i].mDataByteSize);
}
static void WrapBufferList(AudioBufferList* buffer_list,
AudioBus* bus,
int frames) {
DCHECK(buffer_list);
DCHECK(bus);
int channels = bus->channels();
int buffer_list_channels = buffer_list->mNumberBuffers;
// Copy pointers from AudioBufferList.
int source_idx = 0;
for (int i = 0; i < channels; ++i) {
bus->SetChannelData(
i, static_cast<float*>(buffer_list->mBuffers[source_idx].mData));
// It's ok to pass in a |buffer_list| with fewer channels, in which
// case we just duplicate the last channel.
if (source_idx < buffer_list_channels - 1)
++source_idx;
}
// Finally set the actual length.
bus->set_frames(frames);
}
AudioSynchronizedStream::AudioSynchronizedStream(
AudioManagerMac* manager,
const AudioParameters& params,
AudioDeviceID input_id,
AudioDeviceID output_id)
: manager_(manager),
params_(params),
input_sample_rate_(0),
output_sample_rate_(0),
input_id_(input_id),
output_id_(output_id),
input_buffer_list_(NULL),
fifo_(kChannels, kFifoSize),
target_fifo_frames_(kBaseTargetFifoFrames),
average_delta_(0.0),
fifo_rate_compensation_(1.0),
input_unit_(0),
varispeed_unit_(0),
output_unit_(0),
first_input_time_(-1),
is_running_(false),
hardware_buffer_size_(kHardwareBufferSize),
channels_(kChannels) {
VLOG(1) << "AudioSynchronizedStream::AudioSynchronizedStream()";
}
AudioSynchronizedStream::~AudioSynchronizedStream() {
DCHECK(!input_unit_);
DCHECK(!output_unit_);
DCHECK(!varispeed_unit_);
}
bool AudioSynchronizedStream::Open() {
if (params_.channels() != kChannels) {
LOG(ERROR) << "Only stereo output is currently supported.";
return false;
}
// Create the input, output, and varispeed AudioUnits.
OSStatus result = CreateAudioUnits();
if (result != noErr) {
LOG(ERROR) << "Cannot create AudioUnits.";
return false;
}
result = SetupInput(input_id_);
if (result != noErr) {
LOG(ERROR) << "Error configuring input AudioUnit.";
return false;
}
result = SetupOutput(output_id_);
if (result != noErr) {
LOG(ERROR) << "Error configuring output AudioUnit.";
return false;
}
result = SetupCallbacks();
if (result != noErr) {
LOG(ERROR) << "Error setting up callbacks on AudioUnits.";
return false;
}
result = SetupStreamFormats();
if (result != noErr) {
LOG(ERROR) << "Error configuring stream formats on AudioUnits.";
return false;
}
AllocateInputData();
// Final initialization of the AudioUnits.
result = AudioUnitInitialize(input_unit_);
if (result != noErr) {
LOG(ERROR) << "Error initializing input AudioUnit.";
return false;
}
result = AudioUnitInitialize(output_unit_);
if (result != noErr) {
LOG(ERROR) << "Error initializing output AudioUnit.";
return false;
}
result = AudioUnitInitialize(varispeed_unit_);
if (result != noErr) {
LOG(ERROR) << "Error initializing varispeed AudioUnit.";
return false;
}
if (input_sample_rate_ != output_sample_rate_) {
// Add extra safety margin.
target_fifo_frames_ += kAdditionalTargetFifoFrames;
}
// Buffer initial silence corresponding to target I/O buffering.
fifo_.Clear();
scoped_ptr<AudioBus> silence =
AudioBus::Create(channels_, target_fifo_frames_);
silence->Zero();
fifo_.Push(silence.get());
return true;
}
void AudioSynchronizedStream::Close() {
DCHECK(!is_running_);
if (input_buffer_list_) {
free(input_buffer_list_);
input_buffer_list_ = 0;
input_bus_.reset(NULL);
wrapper_bus_.reset(NULL);
}
if (input_unit_) {
AudioUnitUninitialize(input_unit_);
CloseComponent(input_unit_);
}
if (output_unit_) {
AudioUnitUninitialize(output_unit_);
CloseComponent(output_unit_);
}
if (varispeed_unit_) {
AudioUnitUninitialize(varispeed_unit_);
CloseComponent(varispeed_unit_);
}
input_unit_ = NULL;
output_unit_ = NULL;
varispeed_unit_ = NULL;
// Inform the audio manager that we have been closed. This can cause our
// destruction.
manager_->ReleaseOutputStream(this);
}
void AudioSynchronizedStream::Start(AudioSourceCallback* callback) {
DCHECK(callback);
DCHECK(input_unit_);
DCHECK(output_unit_);
DCHECK(varispeed_unit_);
if (is_running_ || !input_unit_ || !output_unit_ || !varispeed_unit_)
return;
source_ = callback;
// Reset state variables each time we Start().
fifo_rate_compensation_ = 1.0;
average_delta_ = 0.0;
OSStatus result = noErr;
if (!is_running_) {
first_input_time_ = -1;
result = AudioOutputUnitStart(input_unit_);
OSSTATUS_DCHECK(result == noErr, result);
if (result == noErr) {
result = AudioOutputUnitStart(output_unit_);
OSSTATUS_DCHECK(result == noErr, result);
}
}
is_running_ = true;
}
void AudioSynchronizedStream::Stop() {
OSStatus result = noErr;
if (is_running_) {
result = AudioOutputUnitStop(input_unit_);
OSSTATUS_DCHECK(result == noErr, result);
if (result == noErr) {
result = AudioOutputUnitStop(output_unit_);
OSSTATUS_DCHECK(result == noErr, result);
}
}
if (result == noErr)
is_running_ = false;
}
bool AudioSynchronizedStream::IsRunning() {
return is_running_;
}
// TODO(crogers): implement - or remove from AudioOutputStream.
void AudioSynchronizedStream::SetVolume(double volume) {}
void AudioSynchronizedStream::GetVolume(double* volume) {}
OSStatus AudioSynchronizedStream::SetOutputDeviceAsCurrent(
AudioDeviceID output_id) {
OSStatus result = noErr;
// Get the default output device if device is unknown.
if (output_id == kAudioDeviceUnknown) {
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
UInt32 size = sizeof(output_id);
result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&pa,
0,
0,
&size,
&output_id);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
}
// Set the render frame size.
UInt32 frame_size = hardware_buffer_size_;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyBufferFrameSize;
pa.mScope = kAudioDevicePropertyScopeInput;
pa.mElement = kAudioObjectPropertyElementMaster;
result = AudioObjectSetPropertyData(
output_id,
&pa,
0,
0,
sizeof(frame_size),
&frame_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
output_info_.Initialize(output_id, false);
// Set the Current Device to the Default Output Unit.
result = AudioUnitSetProperty(
output_unit_,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0,
&output_info_.id_,
sizeof(output_info_.id_));
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
OSStatus AudioSynchronizedStream::SetInputDeviceAsCurrent(
AudioDeviceID input_id) {
OSStatus result = noErr;
// Get the default input device if device is unknown.
if (input_id == kAudioDeviceUnknown) {
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioHardwarePropertyDefaultInputDevice;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
UInt32 size = sizeof(input_id);
result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&pa,
0,
0,
&size,
&input_id);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
}
// Set the render frame size.
UInt32 frame_size = hardware_buffer_size_;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyBufferFrameSize;
pa.mScope = kAudioDevicePropertyScopeInput;
pa.mElement = kAudioObjectPropertyElementMaster;
result = AudioObjectSetPropertyData(
input_id,
&pa,
0,
0,
sizeof(frame_size),
&frame_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
input_info_.Initialize(input_id, true);
// Set the Current Device to the AUHAL.
// This should be done only after I/O has been enabled on the AUHAL.
result = AudioUnitSetProperty(
input_unit_,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0,
&input_info_.id_,
sizeof(input_info_.id_));
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
OSStatus AudioSynchronizedStream::CreateAudioUnits() {
// Q: Why do we need a varispeed unit?
// A: If the input device and the output device are running at
// different sample rates and/or on different clocks, we will need
// to compensate to avoid a pitch change and
// to avoid buffer under and over runs.
ComponentDescription varispeed_desc;
varispeed_desc.componentType = kAudioUnitType_FormatConverter;
varispeed_desc.componentSubType = kAudioUnitSubType_Varispeed;
varispeed_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
varispeed_desc.componentFlags = 0;
varispeed_desc.componentFlagsMask = 0;
Component varispeed_comp = FindNextComponent(NULL, &varispeed_desc);
if (varispeed_comp == NULL)
return -1;
OSStatus result = OpenAComponent(varispeed_comp, &varispeed_unit_);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Open input AudioUnit.
ComponentDescription input_desc;
input_desc.componentType = kAudioUnitType_Output;
input_desc.componentSubType = kAudioUnitSubType_HALOutput;
input_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
input_desc.componentFlags = 0;
input_desc.componentFlagsMask = 0;
Component input_comp = FindNextComponent(NULL, &input_desc);
if (input_comp == NULL)
return -1;
result = OpenAComponent(input_comp, &input_unit_);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Open output AudioUnit.
ComponentDescription output_desc;
output_desc.componentType = kAudioUnitType_Output;
output_desc.componentSubType = kAudioUnitSubType_DefaultOutput;
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
output_desc.componentFlags = 0;
output_desc.componentFlagsMask = 0;
Component output_comp = FindNextComponent(NULL, &output_desc);
if (output_comp == NULL)
return -1;
result = OpenAComponent(output_comp, &output_unit_);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
return noErr;
}
OSStatus AudioSynchronizedStream::SetupInput(AudioDeviceID input_id) {
// The AUHAL used for input needs to be initialized
// before anything is done to it.
OSStatus result = AudioUnitInitialize(input_unit_);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// We must enable the Audio Unit (AUHAL) for input and disable output
// BEFORE setting the AUHAL's current device.
result = EnableIO();
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
result = SetInputDeviceAsCurrent(input_id);
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
OSStatus AudioSynchronizedStream::EnableIO() {
// Enable input on the AUHAL.
UInt32 enable_io = 1;
OSStatus result = AudioUnitSetProperty(
input_unit_,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
1, // input element
&enable_io,
sizeof(enable_io));
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Disable Output on the AUHAL.
enable_io = 0;
result = AudioUnitSetProperty(
input_unit_,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
0, // output element
&enable_io,
sizeof(enable_io));
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
OSStatus AudioSynchronizedStream::SetupOutput(AudioDeviceID output_id) {
OSStatus result = noErr;
result = SetOutputDeviceAsCurrent(output_id);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Tell the output unit not to reset timestamps.
// Otherwise sample rate changes will cause sync loss.
UInt32 start_at_zero = 0;
result = AudioUnitSetProperty(
output_unit_,
kAudioOutputUnitProperty_StartTimestampsAtZero,
kAudioUnitScope_Global,
0,
&start_at_zero,
sizeof(start_at_zero));
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
OSStatus AudioSynchronizedStream::SetupCallbacks() {
// Set the input callback.
AURenderCallbackStruct callback;
callback.inputProc = InputProc;
callback.inputProcRefCon = this;
OSStatus result = AudioUnitSetProperty(
input_unit_,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
0,
&callback,
sizeof(callback));
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Set the output callback.
callback.inputProc = OutputProc;
callback.inputProcRefCon = this;
result = AudioUnitSetProperty(
output_unit_,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&callback,
sizeof(callback));
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Set the varispeed callback.
callback.inputProc = VarispeedProc;
callback.inputProcRefCon = this;
result = AudioUnitSetProperty(
varispeed_unit_,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&callback,
sizeof(callback));
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
OSStatus AudioSynchronizedStream::SetupStreamFormats() {
AudioStreamBasicDescription asbd, asbd_dev1_in, asbd_dev2_out;
// Get the Stream Format (Output client side).
UInt32 property_size = sizeof(asbd_dev1_in);
OSStatus result = AudioUnitGetProperty(
input_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
1,
&asbd_dev1_in,
&property_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Get the Stream Format (client side).
property_size = sizeof(asbd);
result = AudioUnitGetProperty(
input_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
1,
&asbd,
&property_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Get the Stream Format (Output client side).
property_size = sizeof(asbd_dev2_out);
result = AudioUnitGetProperty(
output_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
0,
&asbd_dev2_out,
&property_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Set the format of all the AUs to the input/output devices channel count.
// For a simple case, you want to set this to
// the lower of count of the channels in the input device vs output device.
asbd.mChannelsPerFrame = std::min(asbd_dev1_in.mChannelsPerFrame,
asbd_dev2_out.mChannelsPerFrame);
// We must get the sample rate of the input device and set it to the
// stream format of AUHAL.
Float64 rate = 0;
property_size = sizeof(rate);
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementMaster;
result = AudioObjectGetPropertyData(
input_info_.id_,
&pa,
0,
0,
&property_size,
&rate);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
input_sample_rate_ = rate;
asbd.mSampleRate = rate;
property_size = sizeof(asbd);
// Set the new formats to the AUs...
result = AudioUnitSetProperty(
input_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
1,
&asbd,
property_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
result = AudioUnitSetProperty(
varispeed_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&asbd,
property_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Set the correct sample rate for the output device,
// but keep the channel count the same.
property_size = sizeof(rate);
pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementMaster;
result = AudioObjectGetPropertyData(
output_info_.id_,
&pa,
0,
0,
&property_size,
&rate);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
output_sample_rate_ = rate;
// The requested sample-rate must match the hardware sample-rate.
if (output_sample_rate_ != params_.sample_rate()) {
LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
<< " must match the hardware sample-rate: " << output_sample_rate_;
return kAudioDeviceUnsupportedFormatError;
}
asbd.mSampleRate = rate;
property_size = sizeof(asbd);
// Set the new audio stream formats for the rest of the AUs...
result = AudioUnitSetProperty(
varispeed_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
0,
&asbd,
property_size);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
result = AudioUnitSetProperty(
output_unit_,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&asbd,
property_size);
OSSTATUS_DCHECK(result == noErr, result);
return result;
}
void AudioSynchronizedStream::AllocateInputData() {
// Get the native number of input channels that the hardware supports.
int hardware_channels = 0;
bool got_hardware_channels = AudioManagerMac::GetDeviceChannels(
input_id_, kAudioDevicePropertyScopeInput, &hardware_channels);
if (!got_hardware_channels || hardware_channels > 2) {
// Only mono and stereo are supported on the input side. When it fails to
// get the native channel number or the native channel number is bigger
// than 2, we open the device in stereo mode.
hardware_channels = 2;
}
// Allocate storage for the AudioBufferList used for the
// input data from the input AudioUnit.
// We allocate enough space for with one AudioBuffer per channel.
size_t malloc_size = offsetof(AudioBufferList, mBuffers[0]) +
(sizeof(AudioBuffer) * hardware_channels);
input_buffer_list_ = static_cast<AudioBufferList*>(malloc(malloc_size));
input_buffer_list_->mNumberBuffers = hardware_channels;
input_bus_ = AudioBus::Create(hardware_channels, hardware_buffer_size_);
wrapper_bus_ = AudioBus::CreateWrapper(channels_);
if (hardware_channels != params_.input_channels()) {
ChannelLayout hardware_channel_layout =
GuessChannelLayout(hardware_channels);
ChannelLayout requested_channel_layout =
GuessChannelLayout(params_.input_channels());
channel_mixer_.reset(new ChannelMixer(hardware_channel_layout,
requested_channel_layout));
mixer_bus_ = AudioBus::Create(params_.input_channels(),
hardware_buffer_size_);
}
// Allocate buffers for AudioBufferList.
UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
input_buffer_list_->mBuffers[i].mNumberChannels = 1;
input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
}
}
OSStatus AudioSynchronizedStream::HandleInputCallback(
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
TRACE_EVENT0("audio", "AudioSynchronizedStream::HandleInputCallback");
if (first_input_time_ < 0.0)
first_input_time_ = time_stamp->mSampleTime;
// Get the new audio input data.
OSStatus result = AudioUnitRender(
input_unit_,
io_action_flags,
time_stamp,
bus_number,
number_of_frames,
input_buffer_list_);
// TODO(xians): Add back the DCHECK after synchronize IO supports all
// combination of input and output params. See http://issue/246521.
if (result != noErr)
return result;
// Buffer input into FIFO.
int available_frames = fifo_.max_frames() - fifo_.frames();
if (input_bus_->frames() <= available_frames) {
if (channel_mixer_) {
channel_mixer_->Transform(input_bus_.get(), mixer_bus_.get());
fifo_.Push(mixer_bus_.get());
} else {
fifo_.Push(input_bus_.get());
}
}
return result;
}
OSStatus AudioSynchronizedStream::HandleVarispeedCallback(
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
// Create a wrapper bus on the AudioBufferList.
WrapBufferList(io_data, wrapper_bus_.get(), number_of_frames);
if (fifo_.frames() < static_cast<int>(number_of_frames)) {
// We don't DCHECK here, since this is a possible run-time condition
// if the machine is bogged down.
wrapper_bus_->Zero();
return noErr;
}
// Read from the FIFO to feed the varispeed.
fifo_.Consume(wrapper_bus_.get(), 0, number_of_frames);
return noErr;
}
OSStatus AudioSynchronizedStream::HandleOutputCallback(
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
// Input callback hasn't run yet or we've suddenly changed sample-rates
// -> silence.
if (first_input_time_ < 0.0 ||
static_cast<int>(number_of_frames) != params_.frames_per_buffer()) {
ZeroBufferList(io_data);
return noErr;
}
// Use the varispeed playback rate to offset small discrepancies
// in hardware clocks, and also any differences in sample-rate
// between input and output devices.
// Calculate a varispeed rate scalar factor to compensate for drift between
// input and output. We use the actual number of frames still in the FIFO
// compared with the ideal value of |target_fifo_frames_|.
int delta = fifo_.frames() - target_fifo_frames_;
// Average |delta| because it can jitter back/forth quite frequently
// by +/- the hardware buffer-size *if* the input and output callbacks are
// happening at almost exactly the same time. Also, if the input and output
// sample-rates are different then |delta| will jitter quite a bit due to
// the rate conversion happening in the varispeed, plus the jittering of
// the callbacks. The average value is what's important here.
average_delta_ += (delta - average_delta_) * 0.1;
// Compute a rate compensation which always attracts us back to the
// |target_fifo_frames_| over a period of kCorrectionTimeSeconds.
const double kCorrectionTimeSeconds = 0.1;
double correction_time_frames = kCorrectionTimeSeconds * output_sample_rate_;
fifo_rate_compensation_ =
(correction_time_frames + average_delta_) / correction_time_frames;
// Adjust for FIFO drift.
OSStatus result = AudioUnitSetParameter(
varispeed_unit_,
kVarispeedParam_PlaybackRate,
kAudioUnitScope_Global,
0,
fifo_rate_compensation_,
0);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Render to the output using the varispeed.
result = AudioUnitRender(
varispeed_unit_,
io_action_flags,
time_stamp,
0,
number_of_frames,
io_data);
OSSTATUS_DCHECK(result == noErr, result);
if (result != noErr)
return result;
// Create a wrapper bus on the AudioBufferList.
WrapBufferList(io_data, wrapper_bus_.get(), number_of_frames);
// Process in-place!
source_->OnMoreIOData(wrapper_bus_.get(),
wrapper_bus_.get(),
AudioBuffersState(0, 0));
return noErr;
}
OSStatus AudioSynchronizedStream::InputProc(
void* user_data,
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
AudioSynchronizedStream* stream =
static_cast<AudioSynchronizedStream*>(user_data);
DCHECK(stream);
return stream->HandleInputCallback(
io_action_flags,
time_stamp,
bus_number,
number_of_frames,
io_data);
}
OSStatus AudioSynchronizedStream::VarispeedProc(
void* user_data,
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
AudioSynchronizedStream* stream =
static_cast<AudioSynchronizedStream*>(user_data);
DCHECK(stream);
return stream->HandleVarispeedCallback(
io_action_flags,
time_stamp,
bus_number,
number_of_frames,
io_data);
}
OSStatus AudioSynchronizedStream::OutputProc(
void* user_data,
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 number_of_frames,
AudioBufferList* io_data) {
AudioSynchronizedStream* stream =
static_cast<AudioSynchronizedStream*>(user_data);
DCHECK(stream);
return stream->HandleOutputCallback(
io_action_flags,
time_stamp,
bus_number,
number_of_frames,
io_data);
}
void AudioSynchronizedStream::AudioDeviceInfo::Initialize(
AudioDeviceID id, bool is_input) {
id_ = id;
is_input_ = is_input;
if (id_ == kAudioDeviceUnknown)
return;
UInt32 property_size = sizeof(buffer_size_frames_);
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyBufferFrameSize;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementMaster;
OSStatus result = AudioObjectGetPropertyData(
id_,
&pa,
0,
0,
&property_size,
&buffer_size_frames_);
OSSTATUS_DCHECK(result == noErr, result);
}
} // namespace media