blob: d0e0afab83a1ac33ba04e191bb9ab8717f91b2d3 [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_output_mac.h"
#include <CoreServices/CoreServices.h>
#include "base/basictypes.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "media/audio/audio_util.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/channel_mixer.h"
namespace media {
// A custom data structure to store information an AudioQueue buffer.
struct AudioQueueUserData {
AudioQueueUserData() : empty_buffer(false) {}
bool empty_buffer;
};
// Overview of operation:
// 1) An object of PCMQueueOutAudioOutputStream is created by the AudioManager
// factory: audio_man->MakeAudioStream(). This just fills some structure.
// 2) Next some thread will call Open(), at that point the underliying OS
// queue is created and the audio buffers allocated.
// 3) Then some thread will call Start(source) At this point the source will be
// called to fill the initial buffers in the context of that same thread.
// Then the OS queue is started which will create its own thread which
// periodically will call the source for more data as buffers are being
// consumed.
// 4) At some point some thread will call Stop(), which we handle by directly
// stoping the OS queue.
// 5) One more callback to the source could be delivered in in the context of
// the queue's own thread. Data, if any will be discared.
// 6) The same thread that called stop will call Close() where we cleanup
// and notifiy the audio manager, which likley will destroy this object.
PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream(
AudioManagerMac* manager, const AudioParameters& params)
: audio_queue_(NULL),
source_(NULL),
manager_(manager),
packet_size_(params.GetBytesPerBuffer()),
silence_bytes_(0),
volume_(1),
pending_bytes_(0),
num_source_channels_(params.channels()),
source_layout_(params.channel_layout()),
num_core_channels_(0),
should_swizzle_(false),
stopped_event_(true /* manual reset */, false /* initial state */),
num_buffers_left_(kNumBuffers),
audio_bus_(AudioBus::Create(params)) {
// We must have a manager.
DCHECK(manager_);
// A frame is one sample across all channels. In interleaved audio the per
// frame fields identify the set of n |channels|. In uncompressed audio, a
// packet is always one frame.
format_.mSampleRate = params.sample_rate();
format_.mFormatID = kAudioFormatLinearPCM;
format_.mFormatFlags = kLinearPCMFormatFlagIsPacked;
format_.mBitsPerChannel = params.bits_per_sample();
format_.mChannelsPerFrame = params.channels();
format_.mFramesPerPacket = 1;
format_.mBytesPerPacket = (format_.mBitsPerChannel * params.channels()) / 8;
format_.mBytesPerFrame = format_.mBytesPerPacket;
format_.mReserved = 0;
memset(buffer_, 0, sizeof(buffer_));
memset(core_channel_orderings_, 0, sizeof(core_channel_orderings_));
memset(channel_remap_, 0, sizeof(channel_remap_));
if (params.bits_per_sample() > 8) {
format_.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
}
// Silence buffer has a duration of 6ms to simulate the behavior of Windows.
// This value is choosen by experiments and macs cannot keep up with
// anything less than 6ms.
silence_bytes_ = format_.mBytesPerFrame * params.sample_rate() * 6 / 1000;
}
PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() {
}
void PCMQueueOutAudioOutputStream::HandleError(OSStatus err) {
// source_ can be set to NULL from another thread. We need to cache its
// pointer while we operate here. Note that does not mean that the source
// has been destroyed.
AudioSourceCallback* source = GetSource();
if (source)
source->OnError(this, static_cast<int>(err));
LOG(ERROR) << "error " << GetMacOSStatusErrorString(err)
<< " (" << err << ")";
}
bool PCMQueueOutAudioOutputStream::Open() {
// Get the default device id.
AudioObjectID device_id = 0;
AudioObjectPropertyAddress property_address = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 device_id_size = sizeof(device_id);
OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property_address, 0, NULL,
&device_id_size, &device_id);
if (err != noErr) {
HandleError(err);
return false;
}
// Get the size of the channel layout.
UInt32 core_layout_size;
property_address.mSelector = kAudioDevicePropertyPreferredChannelLayout;
property_address.mScope = kAudioDevicePropertyScopeOutput;
err = AudioObjectGetPropertyDataSize(device_id, &property_address, 0, NULL,
&core_layout_size);
if (err != noErr) {
HandleError(err);
return false;
}
// Get the device's channel layout. This layout may vary in sized based on
// the number of channels. Use |core_layout_size| to allocate memory.
scoped_ptr_malloc<AudioChannelLayout> core_channel_layout;
core_channel_layout.reset(
reinterpret_cast<AudioChannelLayout*>(malloc(core_layout_size)));
memset(core_channel_layout.get(), 0, core_layout_size);
err = AudioObjectGetPropertyData(device_id, &property_address, 0, NULL,
&core_layout_size,
core_channel_layout.get());
if (err != noErr) {
HandleError(err);
return false;
}
num_core_channels_ = std::min(
static_cast<int>(CHANNELS_MAX),
static_cast<int>(core_channel_layout->mNumberChannelDescriptions));
if (num_core_channels_ == 2 &&
ChannelLayoutToChannelCount(source_layout_) > 2) {
channel_mixer_.reset(new ChannelMixer(
source_layout_, CHANNEL_LAYOUT_STEREO));
mixed_audio_bus_ = AudioBus::Create(
num_core_channels_, audio_bus_->frames());
format_.mChannelsPerFrame = num_core_channels_;
format_.mBytesPerFrame = (format_.mBitsPerChannel >> 3) *
format_.mChannelsPerFrame;
format_.mBytesPerPacket = format_.mBytesPerFrame * format_.mFramesPerPacket;
}
// Create the actual queue object and let the OS use its own thread to
// run its CFRunLoop.
err = AudioQueueNewOutput(&format_, RenderCallback, this, NULL,
kCFRunLoopCommonModes, 0, &audio_queue_);
if (err != noErr) {
HandleError(err);
return false;
}
// Allocate the hardware-managed buffers.
for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
err = AudioQueueAllocateBuffer(audio_queue_, packet_size_, &buffer_[ix]);
if (err != noErr) {
HandleError(err);
return false;
}
// Allocate memory for user data.
buffer_[ix]->mUserData = new AudioQueueUserData();
}
// Set initial volume here.
err = AudioQueueSetParameter(audio_queue_, kAudioQueueParam_Volume, 1.0);
if (err != noErr) {
HandleError(err);
return false;
}
// Capture channel layout in a format we can use.
for (int i = 0; i < CHANNELS_MAX; ++i)
core_channel_orderings_[i] = kEmptyChannel;
bool all_channels_unknown = true;
for (int i = 0; i < num_core_channels_; ++i) {
AudioChannelLabel label =
core_channel_layout->mChannelDescriptions[i].mChannelLabel;
if (label == kAudioChannelLabel_Unknown) {
continue;
}
all_channels_unknown = false;
switch (label) {
case kAudioChannelLabel_Left:
core_channel_orderings_[LEFT] = i;
channel_remap_[i] = ChannelOrder(source_layout_, LEFT);
break;
case kAudioChannelLabel_Right:
core_channel_orderings_[RIGHT] = i;
channel_remap_[i] = ChannelOrder(source_layout_, RIGHT);
break;
case kAudioChannelLabel_Center:
core_channel_orderings_[CENTER] = i;
channel_remap_[i] = ChannelOrder(source_layout_, CENTER);
break;
case kAudioChannelLabel_LFEScreen:
core_channel_orderings_[LFE] = i;
channel_remap_[i] = ChannelOrder(source_layout_, LFE);
break;
case kAudioChannelLabel_LeftSurround:
core_channel_orderings_[SIDE_LEFT] = i;
channel_remap_[i] = ChannelOrder(source_layout_, SIDE_LEFT);
break;
case kAudioChannelLabel_RightSurround:
core_channel_orderings_[SIDE_RIGHT] = i;
channel_remap_[i] = ChannelOrder(source_layout_, SIDE_RIGHT);
break;
case kAudioChannelLabel_LeftCenter:
core_channel_orderings_[LEFT_OF_CENTER] = i;
channel_remap_[i] = ChannelOrder(source_layout_, LEFT_OF_CENTER);
break;
case kAudioChannelLabel_RightCenter:
core_channel_orderings_[RIGHT_OF_CENTER] = i;
channel_remap_[i] = ChannelOrder(source_layout_, RIGHT_OF_CENTER);
break;
case kAudioChannelLabel_CenterSurround:
core_channel_orderings_[BACK_CENTER] = i;
channel_remap_[i] = ChannelOrder(source_layout_, BACK_CENTER);
break;
case kAudioChannelLabel_RearSurroundLeft:
core_channel_orderings_[BACK_LEFT] = i;
channel_remap_[i] = ChannelOrder(source_layout_, BACK_LEFT);
break;
case kAudioChannelLabel_RearSurroundRight:
core_channel_orderings_[BACK_RIGHT] = i;
channel_remap_[i] = ChannelOrder(source_layout_, BACK_RIGHT);
break;
default:
DLOG(WARNING) << "Channel label not supported";
channel_remap_[i] = kEmptyChannel;
break;
}
}
if (all_channels_unknown) {
return true;
}
// Check if we need to adjust the layout.
// If the device has a BACK_LEFT and no SIDE_LEFT and the source has
// a SIDE_LEFT but no BACK_LEFT, then move (and preserve the channel).
// e.g. CHANNEL_LAYOUT_5POINT1 -> CHANNEL_LAYOUT_5POINT1_BACK
CheckForAdjustedLayout(SIDE_LEFT, BACK_LEFT);
// Same for SIDE_RIGHT -> BACK_RIGHT.
CheckForAdjustedLayout(SIDE_RIGHT, BACK_RIGHT);
// Move BACK_LEFT to SIDE_LEFT.
// e.g. CHANNEL_LAYOUT_5POINT1_BACK -> CHANNEL_LAYOUT_5POINT1
CheckForAdjustedLayout(BACK_LEFT, SIDE_LEFT);
// Same for BACK_RIGHT -> SIDE_RIGHT.
CheckForAdjustedLayout(BACK_RIGHT, SIDE_RIGHT);
// Move SIDE_LEFT to LEFT_OF_CENTER.
// e.g. CHANNEL_LAYOUT_7POINT1 -> CHANNEL_LAYOUT_7POINT1_WIDE
CheckForAdjustedLayout(SIDE_LEFT, LEFT_OF_CENTER);
// Same for SIDE_RIGHT -> RIGHT_OF_CENTER.
CheckForAdjustedLayout(SIDE_RIGHT, RIGHT_OF_CENTER);
// Move LEFT_OF_CENTER to SIDE_LEFT.
// e.g. CHANNEL_LAYOUT_7POINT1_WIDE -> CHANNEL_LAYOUT_7POINT1
CheckForAdjustedLayout(LEFT_OF_CENTER, SIDE_LEFT);
// Same for RIGHT_OF_CENTER -> SIDE_RIGHT.
CheckForAdjustedLayout(RIGHT_OF_CENTER, SIDE_RIGHT);
// For MONO -> STEREO, move audio to LEFT and RIGHT if applicable.
CheckForAdjustedLayout(CENTER, LEFT);
CheckForAdjustedLayout(CENTER, RIGHT);
// Check if we will need to swizzle from source to device layout (maybe not!).
should_swizzle_ = false;
for (int i = 0; i < num_core_channels_; ++i) {
if (ChannelOrder(source_layout_, static_cast<Channels>(i)) !=
core_channel_orderings_[i]) {
should_swizzle_ = true;
break;
}
}
return true;
}
void PCMQueueOutAudioOutputStream::Close() {
// It is valid to call Close() before calling Open(), thus audio_queue_
// might be NULL.
if (audio_queue_) {
OSStatus err = 0;
for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
if (buffer_[ix]) {
// Free user data.
delete static_cast<AudioQueueUserData*>(buffer_[ix]->mUserData);
// Free AudioQueue buffer.
err = AudioQueueFreeBuffer(audio_queue_, buffer_[ix]);
if (err != noErr) {
HandleError(err);
break;
}
}
}
err = AudioQueueDispose(audio_queue_, true);
if (err != noErr)
HandleError(err);
}
// Inform the audio manager that we have been closed. This can cause our
// destruction.
manager_->ReleaseOutputStream(this);
}
void PCMQueueOutAudioOutputStream::Stop() {
if (source_) {
// We request a synchronous stop, so the next call can take some time. In
// the windows implementation we block here as well.
SetSource(NULL);
stopped_event_.Wait();
}
}
void PCMQueueOutAudioOutputStream::SetVolume(double volume) {
if (!audio_queue_)
return;
volume_ = static_cast<float>(volume);
OSStatus err = AudioQueueSetParameter(audio_queue_,
kAudioQueueParam_Volume,
volume);
if (err != noErr) {
HandleError(err);
}
}
void PCMQueueOutAudioOutputStream::GetVolume(double* volume) {
if (!audio_queue_)
return;
*volume = volume_;
}
template<class Format>
void PCMQueueOutAudioOutputStream::SwizzleLayout(Format* b, uint32 filled) {
Format src_format[num_source_channels_];
int filled_channels = (num_core_channels_ < num_source_channels_) ?
num_core_channels_ : num_source_channels_;
for (uint32 i = 0; i < filled; i += sizeof(src_format),
b += num_source_channels_) {
// TODO(fbarchard): This could be further optimized with pshufb.
memcpy(src_format, b, sizeof(src_format));
for (int ch = 0; ch < filled_channels; ++ch) {
if (channel_remap_[ch] != kEmptyChannel &&
channel_remap_[ch] <= CHANNELS_MAX) {
b[ch] = src_format[channel_remap_[ch]];
} else {
b[ch] = 0;
}
}
}
}
bool PCMQueueOutAudioOutputStream::CheckForAdjustedLayout(
Channels input_channel,
Channels output_channel) {
if (core_channel_orderings_[output_channel] > kEmptyChannel &&
core_channel_orderings_[input_channel] == kEmptyChannel &&
ChannelOrder(source_layout_, input_channel) > kEmptyChannel &&
ChannelOrder(source_layout_, output_channel) == kEmptyChannel) {
channel_remap_[core_channel_orderings_[output_channel]] =
ChannelOrder(source_layout_, input_channel);
return true;
}
return false;
}
// Note to future hackers of this function: Do not add locks to this function
// that are held through any calls made back into AudioQueue APIs, or other
// OS audio functions. This is because the OS dispatch may grab external
// locks, or possibly re-enter this function which can lead to a deadlock.
void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this,
AudioQueueRef queue,
AudioQueueBufferRef buffer) {
TRACE_EVENT0("audio", "PCMQueueOutAudioOutputStream::RenderCallback");
PCMQueueOutAudioOutputStream* audio_stream =
static_cast<PCMQueueOutAudioOutputStream*>(p_this);
// Call the audio source to fill the free buffer with data. Not having a
// source means that the queue has been stopped.
AudioSourceCallback* source = audio_stream->GetSource();
if (!source) {
// PCMQueueOutAudioOutputStream::Stop() is waiting for callback to
// stop the stream and signal when all callbacks are done.
// (we probably can stop the stream there, but it is better to have
// all the complex logic in one place; stopping latency is not very
// important if you reuse audio stream in the mixer and not close it
// immediately).
--audio_stream->num_buffers_left_;
if (audio_stream->num_buffers_left_ == kNumBuffers - 1) {
// First buffer after stop requested, stop the queue.
OSStatus err = AudioQueueStop(audio_stream->audio_queue_, true);
if (err != noErr)
audio_stream->HandleError(err);
}
if (audio_stream->num_buffers_left_ == 0) {
// Now we finally saw all the buffers.
// Signal that stopping is complete.
// Should never touch audio_stream after signaling as it
// can be deleted at any moment.
audio_stream->stopped_event_.Signal();
}
return;
}
// Adjust the number of pending bytes by subtracting the amount played.
if (!static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer)
audio_stream->pending_bytes_ -= buffer->mAudioDataByteSize;
uint32 capacity = buffer->mAudioDataBytesCapacity;
AudioBus* audio_bus = audio_stream->audio_bus_.get();
DCHECK_EQ(
audio_bus->frames() * audio_stream->format_.mBytesPerFrame, capacity);
// TODO(sergeyu): Specify correct hardware delay for AudioBuffersState.
int frames_filled = source->OnMoreData(
audio_bus, AudioBuffersState(audio_stream->pending_bytes_, 0));
uint32 filled = frames_filled * audio_stream->format_.mBytesPerFrame;
// TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer;
// volume adjust should use SSE optimized vector_fmul() prior to interleave.
AudioBus* output_bus = audio_bus;
if (audio_stream->channel_mixer_) {
output_bus = audio_stream->mixed_audio_bus_.get();
audio_stream->channel_mixer_->Transform(audio_bus, output_bus);
}
// Note: If this ever changes to output raw float the data must be clipped
// and sanitized since it may come from an untrusted source such as NaCl.
output_bus->ToInterleaved(
frames_filled, audio_stream->format_.mBitsPerChannel / 8,
buffer->mAudioData);
// In order to keep the callback running, we need to provide a positive amount
// of data to the audio queue. To simulate the behavior of Windows, we write
// a buffer of silence.
if (!filled) {
CHECK(audio_stream->silence_bytes_ <= static_cast<int>(capacity));
filled = audio_stream->silence_bytes_;
// Assume unsigned audio.
int silence_value = 128;
if (audio_stream->format_.mBitsPerChannel > 8) {
// When bits per channel is greater than 8, audio is signed.
silence_value = 0;
}
memset(buffer->mAudioData, silence_value, filled);
static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer = true;
} else if (filled > capacity) {
// User probably overran our buffer.
audio_stream->HandleError(0);
return;
} else {
static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer = false;
}
if (audio_stream->should_swizzle_) {
// Handle channel order for surround sound audio.
if (audio_stream->format_.mBitsPerChannel == 8) {
audio_stream->SwizzleLayout(reinterpret_cast<uint8*>(buffer->mAudioData),
filled);
} else if (audio_stream->format_.mBitsPerChannel == 16) {
audio_stream->SwizzleLayout(reinterpret_cast<int16*>(buffer->mAudioData),
filled);
} else if (audio_stream->format_.mBitsPerChannel == 32) {
audio_stream->SwizzleLayout(reinterpret_cast<int32*>(buffer->mAudioData),
filled);
}
}
buffer->mAudioDataByteSize = filled;
// Increment bytes by amount filled into audio buffer if this is not a
// silence buffer.
if (!static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer)
audio_stream->pending_bytes_ += filled;
if (NULL == queue)
return;
// Queue the audio data to the audio driver.
OSStatus err = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
if (err != noErr) {
if (err == kAudioQueueErr_EnqueueDuringReset) {
// This is the error you get if you try to enqueue a buffer and the
// queue has been closed. Not really a problem if indeed the queue
// has been closed. We recheck the value of source now to see if it has
// indeed been closed.
if (!audio_stream->GetSource())
return;
}
audio_stream->HandleError(err);
}
}
void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) {
DCHECK(callback);
DLOG_IF(ERROR, !audio_queue_) << "Open() has not been called successfully";
if (!audio_queue_)
return;
OSStatus err = noErr;
SetSource(callback);
pending_bytes_ = 0;
stopped_event_.Reset();
num_buffers_left_ = kNumBuffers;
// Ask the source to pre-fill all our buffers before playing.
for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
buffer_[ix]->mAudioDataByteSize = 0;
// Caller waits for 1st packet to become available, but not for others,
// so we wait for them here.
if (ix != 0) {
AudioSourceCallback* source = GetSource();
if (source)
source->WaitTillDataReady();
}
RenderCallback(this, NULL, buffer_[ix]);
}
// Queue the buffers to the audio driver, sounds starts now.
for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
err = AudioQueueEnqueueBuffer(audio_queue_, buffer_[ix], 0, NULL);
if (err != noErr) {
HandleError(err);
return;
}
}
err = AudioQueueStart(audio_queue_, NULL);
if (err != noErr) {
HandleError(err);
return;
}
}
void PCMQueueOutAudioOutputStream::SetSource(AudioSourceCallback* source) {
base::AutoLock lock(source_lock_);
source_ = source;
}
AudioOutputStream::AudioSourceCallback*
PCMQueueOutAudioOutputStream::GetSource() {
base::AutoLock lock(source_lock_);
return source_;
}
} // namespace media