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 |