Chromium Code Reviews| Index: media/audio/mac/audio_unified_mac.cc | 
| =================================================================== | 
| --- media/audio/mac/audio_unified_mac.cc (revision 0) | 
| +++ media/audio/mac/audio_unified_mac.cc (revision 0) | 
| @@ -0,0 +1,356 @@ | 
| +// 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_unified_mac.h" | 
| + | 
| +#include <CoreServices/CoreServices.h> | 
| + | 
| +#include "base/basictypes.h" | 
| +#include "base/logging.h" | 
| +#include "base/mac/mac_logging.h" | 
| +#include "media/audio/audio_util.h" | 
| +#include "media/audio/mac/audio_manager_mac.h" | 
| + | 
| +// !!!! For testing only. | 
| +// Set to 4 to test headphone output on Stanton FinalScratch | 
| +#define DEST_CHANNEL_OFFSET 0 | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
can we remove this or is there some other way to a
 
Chris Rogers
2012/09/10 22:19:06
Yes, this was only for testing -- removed!
 
 | 
| + | 
| +// TODO(crogers): support more than hard-coded stereo input. | 
| +// Ideally we would like to receive this value as a constructor argument. | 
| +const int kDefaultInputChannels = 2; | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
static
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| +namespace media { | 
| + | 
| +AudioHardwareUnifiedStream::AudioHardwareUnifiedStream( | 
| + AudioManagerMac* manager, const AudioParameters& params) | 
| + : manager_(manager), | 
| + source_(NULL), | 
| + volume_(1), | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
nit, 1.0f?
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + input_channels_(0), | 
| + output_channels_(0), | 
| + input_channels_per_frame_(0), | 
| + output_channels_per_frame_(0), | 
| + io_proc_id_(0), | 
| + device_(kAudioObjectUnknown), | 
| + is_playing_(false) { | 
| + // We must have a manager. | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
nit: remove comment (not adding any value)
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + DCHECK(manager_); | 
| + // A frame is one sample across all channels. In interleaved audio the per | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
nit: blank lines before new // comment blocks
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + // 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 | | 
| + kLinearPCMFormatFlagIsSignedInteger; | 
| + 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; | 
| + | 
| + // Calculate the number of sample frames per callback. | 
| + number_of_frames_ = params.GetBytesPerBuffer() / format_.mBytesPerPacket; | 
| + | 
| + client_input_channels_ = kDefaultInputChannels; | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
looks like we can move this into ctor initializer
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + input_bus_ = AudioBus::Create(client_input_channels_, | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
move client_input_channels_ to next line w/ 4 spac
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + params.frames_per_buffer()); | 
| + output_bus_ = AudioBus::Create(params); | 
| +} | 
| + | 
| +AudioHardwareUnifiedStream::~AudioHardwareUnifiedStream() { | 
| + Stop(); | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
fix indent (should be 2 spaces)
 
no longer working on chromium
2012/09/10 12:14:59
It looks like a programming error if the clients d
 
Chris Rogers
2012/09/10 22:19:06
Removed, and added DCHECK
 
 | 
| +} | 
| + | 
| +bool AudioHardwareUnifiedStream::Open() { | 
| + device_ = kAudioDeviceUnknown; | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
nit, device_ has been initialized in the construct
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + // Obtain the current output device selected by the user. | 
| + AudioObjectPropertyAddress pa; | 
| + pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice; | 
| + pa.mScope = kAudioObjectPropertyScopeGlobal; | 
| + pa.mElement = kAudioObjectPropertyElementMaster; | 
| + | 
| + UInt32 size = sizeof(device_); | 
| + | 
| + OSStatus result = AudioObjectGetPropertyData( | 
| + kAudioObjectSystemObject, | 
| + &pa, | 
| + 0, | 
| + 0, | 
| + &size, | 
| + &device_); | 
| + | 
| + if ((result != kAudioHardwareNoError) || (device_ == kAudioDeviceUnknown)) { | 
| + LOG(ERROR) << "Cannot open unified AudioDevice."; | 
| + return false; | 
| + } | 
| + | 
| + // The requested sample-rate must match the hardware sample-rate. | 
| + Float64 sample_rate = 0.0; | 
| + size = sizeof(sample_rate); | 
| + | 
| + pa.mSelector = kAudioDevicePropertyNominalSampleRate; | 
| + pa.mScope = kAudioObjectPropertyScopeWildcard; | 
| + pa.mElement = kAudioObjectPropertyElementMaster; | 
| + | 
| + result = AudioObjectGetPropertyData( | 
| + device_, | 
| + &pa, | 
| + 0, | 
| + 0, | 
| + &size, | 
| + &sample_rate); | 
| + | 
| + if (result != noErr || sample_rate != format_.mSampleRate) { | 
| + LOG(ERROR) << "Requested sample-rate must match the hardware sample-rate."; | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
nit: can you emit sample the two sample rates?
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + return false; | 
| + } | 
| + | 
| + // Configure buffer frame size. | 
| + UInt32 frame_size = number_of_frames_; | 
| + | 
| + pa.mSelector = kAudioDevicePropertyBufferFrameSize; | 
| + pa.mScope = kAudioDevicePropertyScopeInput; | 
| + pa.mElement = kAudioObjectPropertyElementMaster; | 
| + result = AudioObjectSetPropertyData(device_, &pa, 0, 0, | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
nit: fix indent style -- should either look like t
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + sizeof(frame_size), &frame_size); | 
| + | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
add a DCHECK here to verify the result?
 
Chris Rogers
2012/09/10 22:19:06
Fixed: I'm now logging an error here and returning
 
 | 
| + pa.mScope = kAudioDevicePropertyScopeOutput; | 
| + result = AudioObjectSetPropertyData(device_, &pa, 0, 0, | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
ditto
 
no longer working on chromium
2012/09/10 12:14:59
ditto
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + sizeof(frame_size), &frame_size); | 
| + | 
| + DVLOG(1) << "Sample rate: " << sample_rate; | 
| + DVLOG(1) << "Frame size: " << frame_size; | 
| + | 
| + // Determine the number of input and output channels. | 
| + // We handle both the interleaved and non-interleaved cases. | 
| + | 
| + // Get input stream configuration. | 
| + pa.mSelector = kAudioDevicePropertyStreamConfiguration; | 
| + pa.mScope = kAudioDevicePropertyScopeInput; | 
| + pa.mElement = kAudioObjectPropertyElementMaster; | 
| + | 
| + // Allocate storage. | 
| + result = AudioObjectGetPropertyDataSize(device_, &pa, 0, 0, &size); | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
We are not sure if the machine has input device or
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + scoped_array<uint8> input_list_storage(new uint8[size]); | 
| + AudioBufferList& input_list = | 
| + *reinterpret_cast<AudioBufferList*>(input_list_storage.get()); | 
| + | 
| + result = AudioObjectGetPropertyData(device_, &pa, 0, 0, &size, &input_list); | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
DCHECK(result) ?
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + // Determine number of input channels. | 
| + input_channels_per_frame_ = input_list.mNumberBuffers > 0 ? | 
| + input_list.mBuffers[0].mNumberChannels : 0; | 
| + if (input_channels_per_frame_ == 1 && input_list.mNumberBuffers > 1) { | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
We don't need to check input_list.mNumberBuffers >
 
Chris Rogers
2012/09/10 22:19:06
I think it's better to be more explicit since this
 
 | 
| + // Non-interleaved. | 
| + input_channels_ = input_list.mNumberBuffers; | 
| + } else { | 
| + // Interleaved. | 
| + input_channels_ = input_channels_per_frame_; | 
| + } | 
| + | 
| + DVLOG(1) << "Input channels: " << input_channels_; | 
| + DVLOG(1) << "Input channels per frame: " << input_channels_per_frame_; | 
| + | 
| + // The hardware must have at least the requested input channels. | 
| + if (client_input_channels_ > input_channels_) { | 
| + LOG(ERROR) << "AudioDevice does not support requested input channels."; | 
| + return false; | 
| + } | 
| + | 
| + // Get output stream configuration. | 
| + pa.mSelector = kAudioDevicePropertyStreamConfiguration; | 
| + pa.mScope = kAudioDevicePropertyScopeOutput; | 
| + pa.mElement = kAudioObjectPropertyElementMaster; | 
| + | 
| + // Allocate storage. | 
| + AudioObjectGetPropertyDataSize(device_, &pa, 0, 0, &size); | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
do we need to check the return value of these call
 
no longer working on chromium
2012/09/10 12:14:59
DCHECK the size bigger than 0
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + scoped_array<uint8> output_list_storage(new uint8[size]); | 
| + AudioBufferList& output_list = | 
| + *reinterpret_cast<AudioBufferList*>(output_list_storage.get()); | 
| + | 
| + AudioObjectGetPropertyData(device_, &pa, 0, 0, &size, &output_list); | 
| + | 
| + // Determine number of output channels. | 
| + output_channels_per_frame_ = output_list.mBuffers[0].mNumberChannels; | 
| + if (output_channels_per_frame_ == 1 && output_list.mNumberBuffers > 1) { | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
no need to check output_list.mNumberBuffers > 1
 
Chris Rogers
2012/09/10 22:19:06
I just want to be more explicit here.
 
 | 
| + // Non-interleaved. | 
| + output_channels_ = output_list.mNumberBuffers; | 
| + } else { | 
| + // Interleaved. | 
| + output_channels_ = output_channels_per_frame_; | 
| + } | 
| + | 
| + DVLOG(1) << "Output channels: " << output_channels_; | 
| + DVLOG(1) << "Output channels per frame: " << output_channels_per_frame_; | 
| + | 
| + // The hardware must have at least the requested output channels. | 
| + if (output_channels_ < format_.mChannelsPerFrame) { | 
| + LOG(ERROR) << "AudioDevice does not support requested output channels."; | 
| + return false; | 
| + } | 
| + | 
| + // Setup the I/O proc. | 
| + result = AudioDeviceCreateIOProcID(device_, RenderProc, this, &io_proc_id_); | 
| + if (result != noErr) { | 
| + LOG(ERROR) << "Error creating IOProc."; | 
| + return false; | 
| + } | 
| + | 
| + return true; | 
| +} | 
| + | 
| +void AudioHardwareUnifiedStream::Close() { | 
| + Stop(); // make sure to stop if the client forgot. | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
Can we treat this as a programmer error / DCHECK()
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + AudioDeviceDestroyIOProcID(device_, io_proc_id_); | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
set device_ to kAudioObjectUnknown
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + io_proc_id_ = 0; | 
| + | 
| + // Inform the audio manager that we have been closed. This can cause our | 
| + // destruction. | 
| + manager_->ReleaseOutputStream(this); | 
| +} | 
| + | 
| +void AudioHardwareUnifiedStream::Start(AudioSourceCallback* callback) { | 
| + DCHECK(callback); | 
| + DLOG_IF(ERROR, (device_ == kAudioObjectUnknown)) | 
| + << "Open() has not been called successfully"; | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
DCHECK() instead?
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + if (device_ == kAudioObjectUnknown || is_playing_) | 
| + return; | 
| + | 
| + source_ = callback; | 
| + | 
| + // // Allocate storage for our audio source. | 
| + // if (input_channels_) | 
| + // input_data_.reset(new int16[number_of_frames_ * input_channels_]); | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
???
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + // output_data_.reset(new int16[number_of_frames_ * output_channels_]); | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
looks like debugging code, remove?
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + OSStatus result = AudioDeviceStart(device_, io_proc_id_); | 
| + if (result == noErr) | 
| + is_playing_ = true; | 
| +} | 
| + | 
| +void AudioHardwareUnifiedStream::Stop() { | 
| + if (!is_playing_) | 
| + return; | 
| + | 
| + source_ = NULL; | 
| + | 
| + if (device_ != kAudioObjectUnknown) | 
| + AudioDeviceStop(device_, io_proc_id_); | 
| + | 
| + is_playing_ = false; | 
| +} | 
| + | 
| +void AudioHardwareUnifiedStream::SetVolume(double volume) { | 
| + volume_ = static_cast<float>(volume); | 
| + // TODO(crogers): set volume property | 
| +} | 
| + | 
| +void AudioHardwareUnifiedStream::GetVolume(double* volume) { | 
| + *volume = volume_; | 
| +} | 
| + | 
| +// Pulls on our provider with optional input, asking it to render output. | 
| +// Note to future hackers of this function: Do not add locks here because this | 
| +// is running on a real-time thread (for low-latency). | 
| +OSStatus AudioHardwareUnifiedStream::Render( | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
Should we have two Render() methods for interleave
 
Chris Rogers
2012/09/10 22:19:06
The problem is that the interleave vs. non-interle
 
 | 
| + AudioDeviceID inDevice, | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
unix_hacker
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + const AudioTimeStamp* inNow, | 
| + const AudioBufferList* inInputData, | 
| + const AudioTimeStamp* inInputTime, | 
| + AudioBufferList* outOutputData, | 
| + const AudioTimeStamp* inOutputTime) { | 
| + // Convert the input data accounting for possible interleaving. | 
| + // TODO(crogers): its better to simply memcpy() if source is already planar. | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
nit, it's or remove its
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + if (input_channels_ >= client_input_channels_) { | 
| + for (unsigned channel_index = 0; channel_index < client_input_channels_; | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
s/unsigned/int/
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + ++channel_index) { | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
indent one more space
 
no longer working on chromium
2012/09/10 12:14:59
nit, fix the indentation.
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + float* sourceP; | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
source, or source_ptr?
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + unsigned source_channel_index = channel_index; | 
| + | 
| + if (input_channels_per_frame_ > 1) { | 
| + // Interleaved. | 
| + sourceP = static_cast<float*>(inInputData->mBuffers[0].mData) + | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
unix_hacker
perhaps _ptr instead of P?
 
Chris Rogers
2012/09/10 22:19:06
just simplified to |source|
 
 | 
| + source_channel_index; | 
| + } else { | 
| + // Non-interleaved. | 
| + sourceP = static_cast<float*>( | 
| + inInputData->mBuffers[source_channel_index].mData); | 
| + } | 
| + | 
| + float* p = input_bus_->channel(channel_index); | 
| + for (unsigned i = 0; i < number_of_frames_; ++i) { | 
| + p[i] = *sourceP; | 
| + sourceP += input_channels_per_frame_; | 
| + } | 
| + } | 
| + } else if (input_channels_) { | 
| + input_bus_->Zero(); | 
| + } | 
| + | 
| + // Give the client optional input data and have it render the output data. | 
| + source_->OnMoreIOData(input_bus_.get(), | 
| + output_bus_.get(), | 
| + AudioBuffersState(0, 0)); | 
| + | 
| + // TODO(crogers): handle final Core Audio 5.1 layout for 5.1 audio. | 
| + | 
| + // Handle interleaving as necessary. | 
| + // TODO(crogers): its better to simply memcpy() if dest is already planar. | 
| + | 
| + for (unsigned channel_index = 0; channel_index < format_.mChannelsPerFrame; | 
| + ++channel_index) { | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
indent one more space
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + float* destP; | 
| 
 
no longer working on chromium
2012/09/10 12:14:59
dest or dest_ptr
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + | 
| + unsigned dest_channel_index = channel_index + DEST_CHANNEL_OFFSET; | 
| + | 
| + if (output_channels_per_frame_ > 1) { | 
| + // Interleaved. | 
| + destP = static_cast<float*>(outOutputData->mBuffers[0].mData) + | 
| + dest_channel_index; | 
| + } else { | 
| + // Non-interleaved. | 
| + destP = static_cast<float*>( | 
| + outOutputData->mBuffers[dest_channel_index].mData); | 
| + } | 
| + | 
| + float* p = output_bus_->channel(channel_index); | 
| + for (unsigned i = 0; i < number_of_frames_; ++i) { | 
| + *destP = p[i]; | 
| + destP += output_channels_per_frame_; | 
| + } | 
| + } | 
| + | 
| + return noErr; | 
| +} | 
| + | 
| +OSStatus AudioHardwareUnifiedStream::RenderProc( | 
| + AudioDeviceID inDevice, | 
| 
 
scherkus (not reviewing)
2012/09/10 11:26:19
unix_hacker
 
Chris Rogers
2012/09/10 22:19:06
Done.
 
 | 
| + const AudioTimeStamp* inNow, | 
| + const AudioBufferList* inInputData, | 
| + const AudioTimeStamp* inInputTime, | 
| + AudioBufferList* outOutputData, | 
| + const AudioTimeStamp* inOutputTime, | 
| + void* user_data) { | 
| + AudioHardwareUnifiedStream* audio_output = | 
| + static_cast<AudioHardwareUnifiedStream*>(user_data); | 
| + DCHECK(audio_output); | 
| + if (!audio_output) | 
| + return -1; | 
| + | 
| + return audio_output->Render( | 
| + inDevice, | 
| + inNow, | 
| + inInputData, | 
| + inInputTime, | 
| + outOutputData, | 
| + inOutputTime); | 
| +} | 
| + | 
| +} // namespace media |