• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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