1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/audio/mac/audio_auhal_mac.h"
6
7 #include <CoreServices/CoreServices.h>
8
9 #include "base/basictypes.h"
10 #include "base/debug/trace_event.h"
11 #include "base/logging.h"
12 #include "base/mac/mac_logging.h"
13 #include "media/audio/mac/audio_manager_mac.h"
14 #include "media/base/audio_pull_fifo.h"
15
16 namespace media {
17
ZeroBufferList(AudioBufferList * buffer_list)18 static void ZeroBufferList(AudioBufferList* buffer_list) {
19 for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i) {
20 memset(buffer_list->mBuffers[i].mData,
21 0,
22 buffer_list->mBuffers[i].mDataByteSize);
23 }
24 }
25
WrapBufferList(AudioBufferList * buffer_list,AudioBus * bus,int frames)26 static void WrapBufferList(AudioBufferList* buffer_list,
27 AudioBus* bus,
28 int frames) {
29 DCHECK(buffer_list);
30 DCHECK(bus);
31 const int channels = bus->channels();
32 const int buffer_list_channels = buffer_list->mNumberBuffers;
33 CHECK_EQ(channels, buffer_list_channels);
34
35 // Copy pointers from AudioBufferList.
36 for (int i = 0; i < channels; ++i) {
37 bus->SetChannelData(
38 i, static_cast<float*>(buffer_list->mBuffers[i].mData));
39 }
40
41 // Finally set the actual length.
42 bus->set_frames(frames);
43 }
44
AUHALStream(AudioManagerMac * manager,const AudioParameters & params,AudioDeviceID device)45 AUHALStream::AUHALStream(
46 AudioManagerMac* manager,
47 const AudioParameters& params,
48 AudioDeviceID device)
49 : manager_(manager),
50 params_(params),
51 input_channels_(params_.input_channels()),
52 output_channels_(params_.channels()),
53 number_of_frames_(params_.frames_per_buffer()),
54 source_(NULL),
55 device_(device),
56 audio_unit_(0),
57 volume_(1),
58 hardware_latency_frames_(0),
59 stopped_(false),
60 input_buffer_list_(NULL),
61 current_hardware_pending_bytes_(0) {
62 // We must have a manager.
63 DCHECK(manager_);
64
65 VLOG(1) << "AUHALStream::AUHALStream()";
66 VLOG(1) << "Device: " << device;
67 VLOG(1) << "Input channels: " << input_channels_;
68 VLOG(1) << "Output channels: " << output_channels_;
69 VLOG(1) << "Sample rate: " << params_.sample_rate();
70 VLOG(1) << "Buffer size: " << number_of_frames_;
71 }
72
~AUHALStream()73 AUHALStream::~AUHALStream() {
74 }
75
Open()76 bool AUHALStream::Open() {
77 // Get the total number of input and output channels that the
78 // hardware supports.
79 int device_input_channels;
80 bool got_input_channels = AudioManagerMac::GetDeviceChannels(
81 device_,
82 kAudioDevicePropertyScopeInput,
83 &device_input_channels);
84
85 int device_output_channels;
86 bool got_output_channels = AudioManagerMac::GetDeviceChannels(
87 device_,
88 kAudioDevicePropertyScopeOutput,
89 &device_output_channels);
90
91 // Sanity check the requested I/O channels.
92 if (!got_input_channels ||
93 input_channels_ < 0 || input_channels_ > device_input_channels) {
94 LOG(ERROR) << "AudioDevice does not support requested input channels.";
95 return false;
96 }
97
98 if (!got_output_channels ||
99 output_channels_ <= 0 || output_channels_ > device_output_channels) {
100 LOG(ERROR) << "AudioDevice does not support requested output channels.";
101 return false;
102 }
103
104 // The requested sample-rate must match the hardware sample-rate.
105 int sample_rate = AudioManagerMac::HardwareSampleRateForDevice(device_);
106
107 if (sample_rate != params_.sample_rate()) {
108 LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
109 << " must match the hardware sample-rate: " << sample_rate;
110 return false;
111 }
112
113 CreateIOBusses();
114
115 bool configured = ConfigureAUHAL();
116 if (configured)
117 hardware_latency_frames_ = GetHardwareLatency();
118
119 return configured;
120 }
121
Close()122 void AUHALStream::Close() {
123 if (input_buffer_list_) {
124 input_buffer_list_storage_.reset();
125 input_buffer_list_ = NULL;
126 input_bus_.reset(NULL);
127 output_bus_.reset(NULL);
128 }
129
130 if (audio_unit_) {
131 OSStatus result = AudioUnitUninitialize(audio_unit_);
132 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
133 << "AudioUnitUninitialize() failed.";
134 result = AudioComponentInstanceDispose(audio_unit_);
135 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
136 << "AudioComponentInstanceDispose() failed.";
137 }
138
139 // Inform the audio manager that we have been closed. This will cause our
140 // destruction.
141 manager_->ReleaseOutputStream(this);
142 }
143
Start(AudioSourceCallback * callback)144 void AUHALStream::Start(AudioSourceCallback* callback) {
145 DCHECK(callback);
146 if (!audio_unit_) {
147 DLOG(ERROR) << "Open() has not been called successfully";
148 return;
149 }
150
151 stopped_ = false;
152 audio_fifo_.reset();
153 {
154 base::AutoLock auto_lock(source_lock_);
155 source_ = callback;
156 }
157
158 OSStatus result = AudioOutputUnitStart(audio_unit_);
159 if (result == noErr)
160 return;
161
162 Stop();
163 OSSTATUS_DLOG(ERROR, result) << "AudioOutputUnitStart() failed.";
164 callback->OnError(this);
165 }
166
Stop()167 void AUHALStream::Stop() {
168 if (stopped_)
169 return;
170
171 OSStatus result = AudioOutputUnitStop(audio_unit_);
172 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
173 << "AudioOutputUnitStop() failed.";
174 if (result != noErr)
175 source_->OnError(this);
176
177 base::AutoLock auto_lock(source_lock_);
178 source_ = NULL;
179 stopped_ = true;
180 }
181
SetVolume(double volume)182 void AUHALStream::SetVolume(double volume) {
183 volume_ = static_cast<float>(volume);
184 }
185
GetVolume(double * volume)186 void AUHALStream::GetVolume(double* volume) {
187 *volume = volume_;
188 }
189
190 // Pulls on our provider to get rendered audio stream.
191 // Note to future hackers of this function: Do not add locks which can
192 // be contended in the middle of stream processing here (starting and stopping
193 // the stream are ok) because this is running on a real-time thread.
Render(AudioUnitRenderActionFlags * flags,const AudioTimeStamp * output_time_stamp,UInt32 bus_number,UInt32 number_of_frames,AudioBufferList * io_data)194 OSStatus AUHALStream::Render(
195 AudioUnitRenderActionFlags* flags,
196 const AudioTimeStamp* output_time_stamp,
197 UInt32 bus_number,
198 UInt32 number_of_frames,
199 AudioBufferList* io_data) {
200 TRACE_EVENT0("audio", "AUHALStream::Render");
201
202 // If the stream parameters change for any reason, we need to insert a FIFO
203 // since the OnMoreData() pipeline can't handle frame size changes. Generally
204 // this is a temporary situation which can occur after a device change has
205 // occurred but the AudioManager hasn't received the notification yet.
206 if (number_of_frames != number_of_frames_) {
207 // Create a FIFO on the fly to handle any discrepancies in callback rates.
208 if (!audio_fifo_) {
209 VLOG(1) << "Audio frame size change detected; adding FIFO to compensate.";
210 audio_fifo_.reset(new AudioPullFifo(
211 output_channels_,
212 number_of_frames_,
213 base::Bind(&AUHALStream::ProvideInput, base::Unretained(this))));
214 }
215
216 // Synchronous IO is not supported in this state.
217 if (input_channels_ > 0)
218 input_bus_->Zero();
219 } else {
220 if (input_channels_ > 0 && input_buffer_list_) {
221 // Get the input data. |input_buffer_list_| is wrapped
222 // to point to the data allocated in |input_bus_|.
223 OSStatus result = AudioUnitRender(audio_unit_,
224 flags,
225 output_time_stamp,
226 1,
227 number_of_frames,
228 input_buffer_list_);
229 if (result != noErr)
230 ZeroBufferList(input_buffer_list_);
231 }
232 }
233
234 // Make |output_bus_| wrap the output AudioBufferList.
235 WrapBufferList(io_data, output_bus_.get(), number_of_frames);
236
237 // Update the playout latency.
238 const double playout_latency_frames = GetPlayoutLatency(output_time_stamp);
239 current_hardware_pending_bytes_ = static_cast<uint32>(
240 (playout_latency_frames + 0.5) * params_.GetBytesPerFrame());
241
242 if (audio_fifo_)
243 audio_fifo_->Consume(output_bus_.get(), output_bus_->frames());
244 else
245 ProvideInput(0, output_bus_.get());
246
247 return noErr;
248 }
249
ProvideInput(int frame_delay,AudioBus * dest)250 void AUHALStream::ProvideInput(int frame_delay, AudioBus* dest) {
251 base::AutoLock auto_lock(source_lock_);
252 if (!source_) {
253 dest->Zero();
254 return;
255 }
256
257 // Supply the input data and render the output data.
258 source_->OnMoreIOData(
259 input_bus_.get(),
260 dest,
261 AudioBuffersState(0,
262 current_hardware_pending_bytes_ +
263 frame_delay * params_.GetBytesPerFrame()));
264 dest->Scale(volume_);
265 }
266
267 // AUHAL callback.
InputProc(void * user_data,AudioUnitRenderActionFlags * flags,const AudioTimeStamp * output_time_stamp,UInt32 bus_number,UInt32 number_of_frames,AudioBufferList * io_data)268 OSStatus AUHALStream::InputProc(
269 void* user_data,
270 AudioUnitRenderActionFlags* flags,
271 const AudioTimeStamp* output_time_stamp,
272 UInt32 bus_number,
273 UInt32 number_of_frames,
274 AudioBufferList* io_data) {
275 // Dispatch to our class method.
276 AUHALStream* audio_output =
277 static_cast<AUHALStream*>(user_data);
278 if (!audio_output)
279 return -1;
280
281 return audio_output->Render(
282 flags,
283 output_time_stamp,
284 bus_number,
285 number_of_frames,
286 io_data);
287 }
288
GetHardwareLatency()289 double AUHALStream::GetHardwareLatency() {
290 if (!audio_unit_ || device_ == kAudioObjectUnknown) {
291 DLOG(WARNING) << "AudioUnit is NULL or device ID is unknown";
292 return 0.0;
293 }
294
295 // Get audio unit latency.
296 Float64 audio_unit_latency_sec = 0.0;
297 UInt32 size = sizeof(audio_unit_latency_sec);
298 OSStatus result = AudioUnitGetProperty(
299 audio_unit_,
300 kAudioUnitProperty_Latency,
301 kAudioUnitScope_Global,
302 0,
303 &audio_unit_latency_sec,
304 &size);
305 if (result != noErr) {
306 OSSTATUS_DLOG(WARNING, result) << "Could not get AudioUnit latency";
307 return 0.0;
308 }
309
310 // Get output audio device latency.
311 static const AudioObjectPropertyAddress property_address = {
312 kAudioDevicePropertyLatency,
313 kAudioDevicePropertyScopeOutput,
314 kAudioObjectPropertyElementMaster
315 };
316
317 UInt32 device_latency_frames = 0;
318 size = sizeof(device_latency_frames);
319 result = AudioObjectGetPropertyData(
320 device_,
321 &property_address,
322 0,
323 NULL,
324 &size,
325 &device_latency_frames);
326 if (result != noErr) {
327 OSSTATUS_DLOG(WARNING, result) << "Could not get audio device latency";
328 return 0.0;
329 }
330
331 return static_cast<double>((audio_unit_latency_sec *
332 output_format_.mSampleRate) + device_latency_frames);
333 }
334
GetPlayoutLatency(const AudioTimeStamp * output_time_stamp)335 double AUHALStream::GetPlayoutLatency(
336 const AudioTimeStamp* output_time_stamp) {
337 // Ensure mHostTime is valid.
338 if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
339 return 0;
340
341 // Get the delay between the moment getting the callback and the scheduled
342 // time stamp that tells when the data is going to be played out.
343 UInt64 output_time_ns = AudioConvertHostTimeToNanos(
344 output_time_stamp->mHostTime);
345 UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
346
347 // Prevent overflow leading to huge delay information; occurs regularly on
348 // the bots, probably less so in the wild.
349 if (now_ns > output_time_ns)
350 return 0;
351
352 double delay_frames = static_cast<double>
353 (1e-9 * (output_time_ns - now_ns) * output_format_.mSampleRate);
354
355 return (delay_frames + hardware_latency_frames_);
356 }
357
CreateIOBusses()358 void AUHALStream::CreateIOBusses() {
359 if (input_channels_ > 0) {
360 // Allocate storage for the AudioBufferList used for the
361 // input data from the input AudioUnit.
362 // We allocate enough space for with one AudioBuffer per channel.
363 size_t buffer_list_size = offsetof(AudioBufferList, mBuffers[0]) +
364 (sizeof(AudioBuffer) * input_channels_);
365 input_buffer_list_storage_.reset(new uint8[buffer_list_size]);
366
367 input_buffer_list_ =
368 reinterpret_cast<AudioBufferList*>(input_buffer_list_storage_.get());
369 input_buffer_list_->mNumberBuffers = input_channels_;
370
371 // |input_bus_| allocates the storage for the PCM input data.
372 input_bus_ = AudioBus::Create(input_channels_, number_of_frames_);
373
374 // Make the AudioBufferList point to the memory in |input_bus_|.
375 UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
376 for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
377 input_buffer_list_->mBuffers[i].mNumberChannels = 1;
378 input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
379 input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
380 }
381 }
382
383 // The output bus will wrap the AudioBufferList given to us in
384 // the Render() callback.
385 DCHECK_GT(output_channels_, 0);
386 output_bus_ = AudioBus::CreateWrapper(output_channels_);
387 }
388
EnableIO(bool enable,UInt32 scope)389 bool AUHALStream::EnableIO(bool enable, UInt32 scope) {
390 // See Apple technote for details about the EnableIO property.
391 // Note that we use bus 1 for input and bus 0 for output:
392 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
393 UInt32 enable_IO = enable ? 1 : 0;
394 OSStatus result = AudioUnitSetProperty(
395 audio_unit_,
396 kAudioOutputUnitProperty_EnableIO,
397 scope,
398 (scope == kAudioUnitScope_Input) ? 1 : 0,
399 &enable_IO,
400 sizeof(enable_IO));
401 return (result == noErr);
402 }
403
SetStreamFormat(AudioStreamBasicDescription * desc,int channels,UInt32 scope,UInt32 element)404 bool AUHALStream::SetStreamFormat(
405 AudioStreamBasicDescription* desc,
406 int channels,
407 UInt32 scope,
408 UInt32 element) {
409 DCHECK(desc);
410 AudioStreamBasicDescription& format = *desc;
411
412 format.mSampleRate = params_.sample_rate();
413 format.mFormatID = kAudioFormatLinearPCM;
414 format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked |
415 kLinearPCMFormatFlagIsNonInterleaved;
416 format.mBytesPerPacket = sizeof(Float32);
417 format.mFramesPerPacket = 1;
418 format.mBytesPerFrame = sizeof(Float32);
419 format.mChannelsPerFrame = channels;
420 format.mBitsPerChannel = 32;
421 format.mReserved = 0;
422
423 OSStatus result = AudioUnitSetProperty(
424 audio_unit_,
425 kAudioUnitProperty_StreamFormat,
426 scope,
427 element,
428 &format,
429 sizeof(format));
430 return (result == noErr);
431 }
432
ConfigureAUHAL()433 bool AUHALStream::ConfigureAUHAL() {
434 if (device_ == kAudioObjectUnknown ||
435 (input_channels_ == 0 && output_channels_ == 0))
436 return false;
437
438 AudioComponentDescription desc = {
439 kAudioUnitType_Output,
440 kAudioUnitSubType_HALOutput,
441 kAudioUnitManufacturer_Apple,
442 0,
443 0
444 };
445 AudioComponent comp = AudioComponentFindNext(0, &desc);
446 if (!comp)
447 return false;
448
449 OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
450 if (result != noErr) {
451 OSSTATUS_DLOG(ERROR, result) << "AudioComponentInstanceNew() failed.";
452 return false;
453 }
454
455 // Enable input and output as appropriate.
456 if (!EnableIO(input_channels_ > 0, kAudioUnitScope_Input))
457 return false;
458 if (!EnableIO(output_channels_ > 0, kAudioUnitScope_Output))
459 return false;
460
461 // Set the device to be used with the AUHAL AudioUnit.
462 result = AudioUnitSetProperty(
463 audio_unit_,
464 kAudioOutputUnitProperty_CurrentDevice,
465 kAudioUnitScope_Global,
466 0,
467 &device_,
468 sizeof(AudioDeviceID));
469 if (result != noErr)
470 return false;
471
472 // Set stream formats.
473 // See Apple's tech note for details on the peculiar way that
474 // inputs and outputs are handled in the AUHAL concerning scope and bus
475 // (element) numbers:
476 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
477
478 if (input_channels_ > 0) {
479 if (!SetStreamFormat(&input_format_,
480 input_channels_,
481 kAudioUnitScope_Output,
482 1))
483 return false;
484 }
485
486 if (output_channels_ > 0) {
487 if (!SetStreamFormat(&output_format_,
488 output_channels_,
489 kAudioUnitScope_Input,
490 0))
491 return false;
492 }
493
494 // Set the buffer frame size.
495 // WARNING: Setting this value changes the frame size for all audio units in
496 // the current process. It's imperative that the input and output frame sizes
497 // be the same as the frames_per_buffer() returned by
498 // GetDefaultOutputStreamParameters().
499 // See http://crbug.com/154352 for details.
500 UInt32 buffer_size = number_of_frames_;
501 result = AudioUnitSetProperty(
502 audio_unit_,
503 kAudioDevicePropertyBufferFrameSize,
504 kAudioUnitScope_Output,
505 0,
506 &buffer_size,
507 sizeof(buffer_size));
508 if (result != noErr) {
509 OSSTATUS_DLOG(ERROR, result)
510 << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
511 return false;
512 }
513
514 // Setup callback.
515 AURenderCallbackStruct callback;
516 callback.inputProc = InputProc;
517 callback.inputProcRefCon = this;
518 result = AudioUnitSetProperty(
519 audio_unit_,
520 kAudioUnitProperty_SetRenderCallback,
521 kAudioUnitScope_Input,
522 0,
523 &callback,
524 sizeof(callback));
525 if (result != noErr)
526 return false;
527
528 result = AudioUnitInitialize(audio_unit_);
529 if (result != noErr) {
530 OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed.";
531 return false;
532 }
533
534 return true;
535 }
536
537 } // namespace media
538