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