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 <algorithm>
10 #include <cstddef>
11 #include <string>
12 #include <utility>
13 
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/logging.h"
17 #include "base/mac/mac_logging.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/trace_event/trace_event.h"
21 #include "media/audio/mac/audio_manager_mac.h"
22 #include "media/base/audio_pull_fifo.h"
23 #include "media/base/audio_timestamp_helper.h"
24 
25 namespace media {
26 
27 // Mapping from Chrome's channel layout to CoreAudio layout. This must match the
28 // layout of the Channels enum in |channel_layout.h|
29 static const AudioChannelLabel kCoreAudioChannelMapping[] = {
30     kAudioChannelLabel_Left,
31     kAudioChannelLabel_Right,
32     kAudioChannelLabel_Center,
33     kAudioChannelLabel_LFEScreen,
34     kAudioChannelLabel_LeftSurround,
35     kAudioChannelLabel_RightSurround,
36     kAudioChannelLabel_LeftCenter,
37     kAudioChannelLabel_RightCenter,
38     kAudioChannelLabel_CenterSurround,
39     kAudioChannelLabel_LeftSurroundDirect,
40     kAudioChannelLabel_RightSurroundDirect,
41 };
42 static_assert(0 == LEFT && 1 == RIGHT && 2 == CENTER && 3 == LFE &&
43                   4 == BACK_LEFT &&
44                   5 == BACK_RIGHT &&
45                   6 == LEFT_OF_CENTER &&
46                   7 == RIGHT_OF_CENTER &&
47                   8 == BACK_CENTER &&
48                   9 == SIDE_LEFT &&
49                   10 == SIDE_RIGHT &&
50                   10 == CHANNELS_MAX,
51               "Channel positions must match CoreAudio channel order.");
52 
WrapBufferList(AudioBufferList * buffer_list,AudioBus * bus,int frames)53 static void WrapBufferList(AudioBufferList* buffer_list,
54                            AudioBus* bus,
55                            int frames) {
56   const int channels = bus->channels();
57   const int buffer_list_channels = buffer_list->mNumberBuffers;
58   CHECK_EQ(channels, buffer_list_channels);
59 
60   // Copy pointers from AudioBufferList.
61   for (int i = 0; i < channels; ++i)
62     bus->SetChannelData(i, static_cast<float*>(buffer_list->mBuffers[i].mData));
63 
64   // Finally set the actual length.
65   bus->set_frames(frames);
66 }
67 
68 // Sets the stream format on the AUHAL to PCM Float32 non-interleaved for the
69 // given number of channels on the given scope and element. The created stream
70 // description will be stored in |desc|.
SetStreamFormat(int channels,int sample_rate,AudioUnit audio_unit,AudioStreamBasicDescription * format)71 static bool SetStreamFormat(int channels,
72                             int sample_rate,
73                             AudioUnit audio_unit,
74                             AudioStreamBasicDescription* format) {
75   format->mSampleRate = sample_rate;
76   format->mFormatID = kAudioFormatLinearPCM;
77   format->mFormatFlags =
78       kAudioFormatFlagsNativeFloatPacked | kLinearPCMFormatFlagIsNonInterleaved;
79   format->mBytesPerPacket = sizeof(Float32);
80   format->mFramesPerPacket = 1;
81   format->mBytesPerFrame = sizeof(Float32);
82   format->mChannelsPerFrame = channels;
83   format->mBitsPerChannel = 32;
84   format->mReserved = 0;
85 
86   // Set stream formats. See Apple's tech note for details on the peculiar way
87   // that inputs and outputs are handled in the AUHAL concerning scope and bus
88   // (element) numbers:
89   // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
90   return AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
91                               kAudioUnitScope_Input, AUElement::OUTPUT, format,
92                               sizeof(*format)) == noErr;
93 }
94 
95 // Converts |channel_layout| into CoreAudio format and sets up the AUHAL with
96 // our layout information so it knows how to remap the channels.
SetAudioChannelLayout(int channels,ChannelLayout channel_layout,AudioUnit audio_unit)97 static void SetAudioChannelLayout(int channels,
98                                   ChannelLayout channel_layout,
99                                   AudioUnit audio_unit) {
100   DCHECK(audio_unit);
101   DCHECK_GT(channels, 0);
102   DCHECK_GT(channel_layout, CHANNEL_LAYOUT_UNSUPPORTED);
103 
104   // AudioChannelLayout is structure ending in a variable length array, so we
105   // can't directly allocate one. Instead compute the size and and allocate one
106   // inside of a byte array.
107   //
108   // Code modeled after example from Apple documentation here:
109   // https://developer.apple.com/library/content/qa/qa1627/_index.html
110   const size_t layout_size =
111       offsetof(AudioChannelLayout, mChannelDescriptions[channels]);
112   std::unique_ptr<uint8_t[]> layout_storage(new uint8_t[layout_size]);
113   memset(layout_storage.get(), 0, layout_size);
114   AudioChannelLayout* coreaudio_layout =
115       reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
116 
117   coreaudio_layout->mNumberChannelDescriptions = channels;
118   coreaudio_layout->mChannelLayoutTag =
119       kAudioChannelLayoutTag_UseChannelDescriptions;
120   AudioChannelDescription* descriptions =
121       coreaudio_layout->mChannelDescriptions;
122 
123   if (channel_layout == CHANNEL_LAYOUT_DISCRETE) {
124     // For the discrete case just assume common input mappings; once we run out
125     // of known channels mark them as unknown.
126     for (int ch = 0; ch < channels; ++ch) {
127       descriptions[ch].mChannelLabel = ch > CHANNELS_MAX
128                                            ? kAudioChannelLabel_Unknown
129                                            : kCoreAudioChannelMapping[ch];
130       descriptions[ch].mChannelFlags = kAudioChannelFlags_AllOff;
131     }
132   } else if (channel_layout == CHANNEL_LAYOUT_MONO) {
133     // CoreAudio has a special label for mono.
134     DCHECK_EQ(channels, 1);
135     descriptions[0].mChannelLabel = kAudioChannelLabel_Mono;
136     descriptions[0].mChannelFlags = kAudioChannelFlags_AllOff;
137   } else {
138     for (int ch = 0; ch <= CHANNELS_MAX; ++ch) {
139       const int order = ChannelOrder(channel_layout, static_cast<Channels>(ch));
140       if (order == -1)
141         continue;
142       descriptions[order].mChannelLabel = kCoreAudioChannelMapping[ch];
143       descriptions[order].mChannelFlags = kAudioChannelFlags_AllOff;
144     }
145   }
146 
147   OSStatus result = AudioUnitSetProperty(
148       audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input,
149       AUElement::OUTPUT, coreaudio_layout, layout_size);
150   if (result != noErr) {
151     OSSTATUS_DLOG(ERROR, result)
152         << "Failed to set audio channel layout. Using default layout.";
153   }
154 }
155 
AUHALStream(AudioManagerMac * manager,const AudioParameters & params,AudioDeviceID device,const AudioManager::LogCallback & log_callback)156 AUHALStream::AUHALStream(AudioManagerMac* manager,
157                          const AudioParameters& params,
158                          AudioDeviceID device,
159                          const AudioManager::LogCallback& log_callback)
160     : manager_(manager),
161       params_(params),
162       number_of_frames_(params_.frames_per_buffer()),
163       number_of_frames_requested_(0),
164       source_(NULL),
165       device_(device),
166       volume_(1),
167       stopped_(true),
168       current_lost_frames_(0),
169       last_sample_time_(0.0),
170       last_number_of_frames_(0),
171       total_lost_frames_(0),
172       largest_glitch_frames_(0),
173       glitches_detected_(0),
174       log_callback_(log_callback) {
175   // We must have a manager.
176   DCHECK(manager_);
177   DCHECK(params_.IsValid());
178   DCHECK_NE(device, kAudioObjectUnknown);
179 }
180 
~AUHALStream()181 AUHALStream::~AUHALStream() {
182   DCHECK(thread_checker_.CalledOnValidThread());
183   CHECK(!audio_unit_);
184 
185   ReportAndResetStats();
186 }
187 
Open()188 bool AUHALStream::Open() {
189   DCHECK(thread_checker_.CalledOnValidThread());
190   DCHECK(!output_bus_);
191   DCHECK(!audio_unit_);
192 
193   // The output bus will wrap the AudioBufferList given to us in
194   // the Render() callback.
195   output_bus_ = AudioBus::CreateWrapper(params_.channels());
196 
197   bool configured = ConfigureAUHAL();
198   if (configured) {
199     DCHECK(audio_unit_);
200     DCHECK(audio_unit_->is_valid());
201     hardware_latency_ = AudioManagerMac::GetHardwareLatency(
202         audio_unit_->audio_unit(), device_, kAudioDevicePropertyScopeOutput,
203         params_.sample_rate());
204   }
205 
206   return configured;
207 }
208 
Close()209 void AUHALStream::Close() {
210   DCHECK(thread_checker_.CalledOnValidThread());
211   audio_unit_.reset();
212   // Inform the audio manager that we have been closed. This will cause our
213   // destruction. Also include the device ID as a signal to the audio manager
214   // that it should try to increase the native I/O buffer size after the stream
215   // has been closed.
216   manager_->ReleaseOutputStreamUsingRealDevice(this, device_);
217 }
218 
Start(AudioSourceCallback * callback)219 void AUHALStream::Start(AudioSourceCallback* callback) {
220   DCHECK(thread_checker_.CalledOnValidThread());
221   DCHECK(callback);
222   if (!audio_unit_) {
223     DLOG(ERROR) << "Open() has not been called successfully";
224     return;
225   }
226 
227   if (!stopped_) {
228     CHECK_EQ(source_, callback);
229     return;
230   }
231 
232   // Check if we should defer Start() for http://crbug.com/160920.
233   if (manager_->ShouldDeferStreamStart()) {
234     // Use a cancellable closure so that if Stop() is called before Start()
235     // actually runs, we can cancel the pending start.
236     deferred_start_cb_.Reset(
237         base::BindOnce(&AUHALStream::Start, base::Unretained(this), callback));
238     manager_->GetTaskRunner()->PostDelayedTask(
239         FROM_HERE, deferred_start_cb_.callback(),
240         base::TimeDelta::FromSeconds(
241             AudioManagerMac::kStartDelayInSecsForPowerEvents));
242     return;
243   }
244 
245   stopped_ = false;
246   audio_fifo_.reset();
247   source_ = callback;
248 
249   OSStatus result = AudioOutputUnitStart(audio_unit_->audio_unit());
250   if (result == noErr)
251     return;
252 
253   Stop();
254   OSSTATUS_DLOG(ERROR, result) << "AudioOutputUnitStart() failed.";
255   callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
256 }
257 
258 // This stream is always used with sub second buffer sizes, where it's
259 // sufficient to simply always flush upon Start().
Flush()260 void AUHALStream::Flush() {}
261 
Stop()262 void AUHALStream::Stop() {
263   DCHECK(thread_checker_.CalledOnValidThread());
264   deferred_start_cb_.Cancel();
265   if (stopped_)
266     return;
267 
268   OSStatus result = AudioOutputUnitStop(audio_unit_->audio_unit());
269   OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
270       << "AudioOutputUnitStop() failed.";
271   if (result != noErr)
272     source_->OnError(AudioSourceCallback::ErrorType::kUnknown);
273   ReportAndResetStats();
274   source_ = nullptr;
275   stopped_ = true;
276 }
277 
SetVolume(double volume)278 void AUHALStream::SetVolume(double volume) {
279   volume_ = static_cast<float>(volume);
280 }
281 
GetVolume(double * volume)282 void AUHALStream::GetVolume(double* volume) {
283   *volume = volume_;
284 }
285 
286 // Pulls on our provider to get rendered audio stream.
287 // Note to future hackers of this function: Do not add locks which can
288 // be contended in the middle of stream processing here (starting and stopping
289 // 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)290 OSStatus AUHALStream::Render(AudioUnitRenderActionFlags* flags,
291                              const AudioTimeStamp* output_time_stamp,
292                              UInt32 bus_number,
293                              UInt32 number_of_frames,
294                              AudioBufferList* data) {
295   TRACE_EVENT2("audio", "AUHALStream::Render", "input buffer size",
296                number_of_frames_, "output buffer size", number_of_frames);
297 
298   UpdatePlayoutTimestamp(output_time_stamp);
299 
300   // If the stream parameters change for any reason, we need to insert a FIFO
301   // since the OnMoreData() pipeline can't handle frame size changes.
302   if (number_of_frames != number_of_frames_) {
303     // Create a FIFO on the fly to handle any discrepancies in callback rates.
304     if (!audio_fifo_) {
305       // TODO(grunell): We'll only care about the first buffer size change,
306       // any further changes will be ignored. It would be nice to have all
307       // changes reflected in UMA stats.
308       number_of_frames_requested_ = number_of_frames;
309       DVLOG(1) << "Audio frame size changed from " << number_of_frames_
310                << " to " << number_of_frames << " adding FIFO to compensate.";
311       audio_fifo_.reset(
312           new AudioPullFifo(params_.channels(), number_of_frames_,
313                             base::BindRepeating(&AUHALStream::ProvideInput,
314                                                 base::Unretained(this))));
315     }
316   }
317 
318   // Make |output_bus_| wrap the output AudioBufferList.
319   WrapBufferList(data, output_bus_.get(), number_of_frames);
320 
321   current_playout_time_ = GetPlayoutTime(output_time_stamp);
322 
323   if (audio_fifo_)
324     audio_fifo_->Consume(output_bus_.get(), output_bus_->frames());
325   else
326     ProvideInput(0, output_bus_.get());
327 
328   last_number_of_frames_ = number_of_frames;
329 
330   return noErr;
331 }
332 
ProvideInput(int frame_delay,AudioBus * dest)333 void AUHALStream::ProvideInput(int frame_delay, AudioBus* dest) {
334   DCHECK(source_);
335 
336   const base::TimeTicks playout_time =
337       current_playout_time_ +
338       AudioTimestampHelper::FramesToTime(frame_delay, params_.sample_rate());
339   const base::TimeTicks now = base::TimeTicks::Now();
340   const base::TimeDelta delay = playout_time - now;
341 
342   // Supply the input data and render the output data.
343   source_->OnMoreData(delay, now, current_lost_frames_, dest);
344   dest->Scale(volume_);
345   current_lost_frames_ = 0;
346 }
347 
348 // AUHAL callback.
InputProc(void * user_data,AudioUnitRenderActionFlags * flags,const AudioTimeStamp * output_time_stamp,UInt32 bus_number,UInt32 number_of_frames,AudioBufferList * io_data)349 OSStatus AUHALStream::InputProc(void* user_data,
350                                 AudioUnitRenderActionFlags* flags,
351                                 const AudioTimeStamp* output_time_stamp,
352                                 UInt32 bus_number,
353                                 UInt32 number_of_frames,
354                                 AudioBufferList* io_data) {
355   // Dispatch to our class method.
356   AUHALStream* audio_output = static_cast<AUHALStream*>(user_data);
357   if (!audio_output)
358     return -1;
359 
360   return audio_output->Render(flags, output_time_stamp, bus_number,
361                               number_of_frames, io_data);
362 }
363 
GetPlayoutTime(const AudioTimeStamp * output_time_stamp)364 base::TimeTicks AUHALStream::GetPlayoutTime(
365     const AudioTimeStamp* output_time_stamp) {
366   // A platform bug has been observed where the platform sometimes reports that
367   // the next frames will be output at an invalid time or a time in the past.
368   // Because the target playout time cannot be invalid or in the past, return
369   // "now" in these cases.
370   if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
371     return base::TimeTicks::Now();
372 
373   return std::max(base::TimeTicks::FromMachAbsoluteTime(
374                       output_time_stamp->mHostTime),
375                   base::TimeTicks::Now()) +
376          hardware_latency_;
377 }
378 
UpdatePlayoutTimestamp(const AudioTimeStamp * timestamp)379 void AUHALStream::UpdatePlayoutTimestamp(const AudioTimeStamp* timestamp) {
380   if ((timestamp->mFlags & kAudioTimeStampSampleTimeValid) == 0)
381     return;
382 
383   if (last_sample_time_) {
384     DCHECK_NE(0U, last_number_of_frames_);
385     UInt32 diff =
386         static_cast<UInt32>(timestamp->mSampleTime - last_sample_time_);
387     if (diff != last_number_of_frames_) {
388       DCHECK_GT(diff, last_number_of_frames_);
389       // We're being asked to render samples post what we expected. Update the
390       // glitch count etc and keep a record of the largest glitch.
391       auto lost_frames = diff - last_number_of_frames_;
392       total_lost_frames_ += lost_frames;
393       current_lost_frames_ += lost_frames;
394       if (lost_frames > largest_glitch_frames_)
395         largest_glitch_frames_ = lost_frames;
396       ++glitches_detected_;
397     }
398   }
399 
400   // Store the last sample time for use next time we get called back.
401   last_sample_time_ = timestamp->mSampleTime;
402 }
403 
ReportAndResetStats()404 void AUHALStream::ReportAndResetStats() {
405   if (!last_sample_time_)
406     return;  // No stats gathered to report.
407 
408   // A value of 0 indicates that we got the buffer size we asked for.
409   UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.FramesRequested",
410                           number_of_frames_requested_);
411   // Even if there aren't any glitches, we want to record it to get a feel for
412   // how often we get no glitches vs the alternative.
413   UMA_HISTOGRAM_CUSTOM_COUNTS("Media.Audio.Render.Glitches", glitches_detected_,
414                               1, 999999, 100);
415 
416   auto lost_frames_ms = (total_lost_frames_ * 1000) / params_.sample_rate();
417 
418   std::string log_message = base::StringPrintf(
419       "AU out: Total glitches=%d. Total frames lost=%d (%d ms).",
420       glitches_detected_, total_lost_frames_, lost_frames_ms);
421 
422   if (!log_callback_.is_null())
423     log_callback_.Run(log_message);
424 
425   if (glitches_detected_ != 0) {
426     UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.LostFramesInMs",
427                             lost_frames_ms);
428     auto largest_glitch_ms =
429         (largest_glitch_frames_ * 1000) / params_.sample_rate();
430     UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.LargestGlitchMs",
431                             largest_glitch_ms);
432     DLOG(WARNING) << log_message;
433   }
434 
435   number_of_frames_requested_ = 0;
436   glitches_detected_ = 0;
437   last_sample_time_ = 0;
438   last_number_of_frames_ = 0;
439   total_lost_frames_ = 0;
440   largest_glitch_frames_ = 0;
441 }
442 
ConfigureAUHAL()443 bool AUHALStream::ConfigureAUHAL() {
444   DCHECK(thread_checker_.CalledOnValidThread());
445 
446   std::unique_ptr<ScopedAudioUnit> local_audio_unit(
447       new ScopedAudioUnit(device_, AUElement::OUTPUT));
448   if (!local_audio_unit->is_valid())
449     return false;
450 
451   if (!SetStreamFormat(params_.channels(), params_.sample_rate(),
452                        local_audio_unit->audio_unit(), &output_format_)) {
453     return false;
454   }
455 
456   bool size_was_changed = false;
457   size_t io_buffer_frame_size = 0;
458   if (!manager_->MaybeChangeBufferSize(device_, local_audio_unit->audio_unit(),
459                                        0, number_of_frames_, &size_was_changed,
460                                        &io_buffer_frame_size)) {
461     return false;
462   }
463 
464   // Setup callback.
465   AURenderCallbackStruct callback;
466   callback.inputProc = InputProc;
467   callback.inputProcRefCon = this;
468   OSStatus result = AudioUnitSetProperty(
469       local_audio_unit->audio_unit(), kAudioUnitProperty_SetRenderCallback,
470       kAudioUnitScope_Input, AUElement::OUTPUT, &callback, sizeof(callback));
471   if (result != noErr)
472     return false;
473 
474   SetAudioChannelLayout(params_.channels(), params_.channel_layout(),
475                         local_audio_unit->audio_unit());
476 
477   result = AudioUnitInitialize(local_audio_unit->audio_unit());
478   if (result != noErr) {
479     OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed.";
480     return false;
481   }
482 
483   audio_unit_ = std::move(local_audio_unit);
484   return true;
485 }
486 
487 }  // namespace media
488