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/callback_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