| // 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 |