-
Notifications
You must be signed in to change notification settings - Fork 128
/
Copy pathcubeb_audiounit.cpp
3708 lines (3274 loc) · 121 KB
/
cubeb_audiounit.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright © 2011 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#undef NDEBUG
#include <AudioUnit/AudioUnit.h>
#include <TargetConditionals.h>
#include <assert.h>
#include <mach/mach_time.h>
#include <pthread.h>
#include <stdlib.h>
#if !TARGET_OS_IPHONE
#include <AvailabilityMacros.h>
#include <CoreAudio/AudioHardware.h>
#include <CoreAudio/HostTime.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudioTypes.h>
#if !TARGET_OS_IPHONE
#include "cubeb_osx_run_loop.h"
#endif
#include "cubeb_resampler.h"
#include "cubeb_ring_array.h"
#include <algorithm>
#include <atomic>
#include <set>
#include <string>
#include <sys/time.h>
#include <vector>
using namespace std;
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
typedef UInt32 AudioFormatFlags;
#endif
#define AU_OUT_BUS 0
#define AU_IN_BUS 1
const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice";
#ifdef ALOGV
#undef ALOGV
#endif
#define ALOGV(msg, ...) \
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
^{ \
LOGV(msg, ##__VA_ARGS__); \
})
#ifdef ALOG
#undef ALOG
#endif
#define ALOG(msg, ...) \
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
^{ \
LOG(msg, ##__VA_ARGS__); \
})
/* Testing empirically, some headsets report a minimal latency that is very
* low, but this does not work in practice. Lie and say the minimum is 256
* frames. */
const uint32_t SAFE_MIN_LATENCY_FRAMES = 128;
const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = {
kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = {
kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = {
kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = {
kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMaster};
const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster};
typedef uint32_t device_flags_value;
enum device_flags {
DEV_UNKNOWN = 0x00, /* Unknown */
DEV_INPUT = 0x01, /* Record device like mic */
DEV_OUTPUT = 0x02, /* Playback device like speakers */
DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
DEV_SELECTED_DEFAULT =
0x08, /* User selected to use the system default device */
};
void
audiounit_stream_stop_internal(cubeb_stream * stm);
static int
audiounit_stream_start_internal(cubeb_stream * stm);
static void
audiounit_close_stream(cubeb_stream * stm);
static int
audiounit_setup_stream(cubeb_stream * stm);
static vector<AudioObjectID>
audiounit_get_devices_of_type(cubeb_device_type devtype);
static UInt32
audiounit_get_device_presentation_latency(AudioObjectID devid,
AudioObjectPropertyScope scope);
#if !TARGET_OS_IPHONE
static AudioObjectID
audiounit_get_default_device_id(cubeb_device_type type);
static int
audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
static int
audiounit_uninstall_system_changed_callback(cubeb_stream * stm);
static void
audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags);
#endif
extern cubeb_ops const audiounit_ops;
struct cubeb {
cubeb_ops const * ops = &audiounit_ops;
owned_critical_section mutex;
int active_streams = 0;
uint32_t global_latency_frames = 0;
cubeb_device_collection_changed_callback input_collection_changed_callback =
nullptr;
void * input_collection_changed_user_ptr = nullptr;
cubeb_device_collection_changed_callback output_collection_changed_callback =
nullptr;
void * output_collection_changed_user_ptr = nullptr;
// Store list of devices to detect changes
vector<AudioObjectID> input_device_array;
vector<AudioObjectID> output_device_array;
// The queue should be released when it’s no longer needed.
dispatch_queue_t serial_queue =
dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
// Current used channel layout
atomic<cubeb_channel_layout> layout{CUBEB_LAYOUT_UNDEFINED};
uint32_t channels = 0;
};
static unique_ptr<AudioChannelLayout, decltype(&free)>
make_sized_audio_channel_layout(size_t sz)
{
assert(sz >= sizeof(AudioChannelLayout));
AudioChannelLayout * acl =
reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
assert(acl); // Assert the allocation works.
return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
}
enum class io_side {
INPUT,
OUTPUT,
};
static char const *
to_string(io_side side)
{
switch (side) {
case io_side::INPUT:
return "input";
case io_side::OUTPUT:
return "output";
}
}
struct device_info {
AudioDeviceID id = kAudioObjectUnknown;
device_flags_value flags = DEV_UNKNOWN;
};
struct property_listener {
AudioDeviceID device_id;
const AudioObjectPropertyAddress * property_address;
AudioObjectPropertyListenerProc callback;
cubeb_stream * stream;
property_listener(AudioDeviceID id,
const AudioObjectPropertyAddress * address,
AudioObjectPropertyListenerProc proc, cubeb_stream * stm)
: device_id(id), property_address(address), callback(proc), stream(stm)
{
}
};
struct cubeb_stream {
explicit cubeb_stream(cubeb * context);
/* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
void * user_ptr = nullptr;
/**/
cubeb_data_callback data_callback = nullptr;
cubeb_state_callback state_callback = nullptr;
cubeb_device_changed_callback device_changed_callback = nullptr;
owned_critical_section device_changed_callback_lock;
/* Stream creation parameters */
cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
CUBEB_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE};
cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
CUBEB_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE};
device_info input_device;
device_info output_device;
/* Format descriptions */
AudioStreamBasicDescription input_desc;
AudioStreamBasicDescription output_desc;
/* I/O AudioUnits */
AudioUnit input_unit = nullptr;
AudioUnit output_unit = nullptr;
/* I/O device sample rate */
Float64 input_hw_rate = 0;
Float64 output_hw_rate = 0;
/* Expected I/O thread interleave,
* calculated from I/O hw rate. */
int expected_output_callbacks_in_a_row = 0;
owned_critical_section mutex;
// Hold the input samples in every input callback iteration.
// Only accessed on input/output callback thread and during initial configure.
unique_ptr<auto_array_wrapper> input_linear_buffer;
/* Frame counters */
atomic<uint64_t> frames_played{0};
uint64_t frames_queued = 0;
// How many frames got read from the input since the stream started (includes
// padded silence)
atomic<int64_t> frames_read{0};
// How many frames got written to the output device since the stream started
atomic<int64_t> frames_written{0};
atomic<bool> shutdown{true};
atomic<bool> draining{false};
atomic<bool> reinit_pending{false};
atomic<bool> destroy_pending{false};
/* Latency requested by the user. */
uint32_t latency_frames = 0;
atomic<uint32_t> current_latency_frames{0};
atomic<uint32_t> total_output_latency_frames{0};
unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
/* This is true if a device change callback is currently running. */
atomic<bool> switching_device{false};
atomic<bool> buffer_size_change_state{false};
AudioDeviceID aggregate_device_id =
kAudioObjectUnknown; // the aggregate device id
AudioObjectID plugin_id =
kAudioObjectUnknown; // used to create aggregate device
/* Mixer interface */
unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;
/* Buffer where remixing/resampling will occur when upmixing is required */
/* Only accessed from callback thread */
unique_ptr<uint8_t[]> temp_buffer;
size_t temp_buffer_size = 0; // size in bytes.
/* Listeners indicating what system events are monitored. */
unique_ptr<property_listener> default_input_listener;
unique_ptr<property_listener> default_output_listener;
unique_ptr<property_listener> input_alive_listener;
unique_ptr<property_listener> input_source_listener;
unique_ptr<property_listener> output_source_listener;
};
bool
has_input(cubeb_stream * stm)
{
return stm->input_stream_params.rate != 0;
}
bool
has_output(cubeb_stream * stm)
{
return stm->output_stream_params.rate != 0;
}
cubeb_channel
channel_label_to_cubeb_channel(UInt32 label)
{
switch (label) {
case kAudioChannelLabel_Left:
return CHANNEL_FRONT_LEFT;
case kAudioChannelLabel_Right:
return CHANNEL_FRONT_RIGHT;
case kAudioChannelLabel_Center:
return CHANNEL_FRONT_CENTER;
case kAudioChannelLabel_LFEScreen:
return CHANNEL_LOW_FREQUENCY;
case kAudioChannelLabel_LeftSurround:
return CHANNEL_BACK_LEFT;
case kAudioChannelLabel_RightSurround:
return CHANNEL_BACK_RIGHT;
case kAudioChannelLabel_LeftCenter:
return CHANNEL_FRONT_LEFT_OF_CENTER;
case kAudioChannelLabel_RightCenter:
return CHANNEL_FRONT_RIGHT_OF_CENTER;
case kAudioChannelLabel_CenterSurround:
return CHANNEL_BACK_CENTER;
case kAudioChannelLabel_LeftSurroundDirect:
return CHANNEL_SIDE_LEFT;
case kAudioChannelLabel_RightSurroundDirect:
return CHANNEL_SIDE_RIGHT;
case kAudioChannelLabel_TopCenterSurround:
return CHANNEL_TOP_CENTER;
case kAudioChannelLabel_VerticalHeightLeft:
return CHANNEL_TOP_FRONT_LEFT;
case kAudioChannelLabel_VerticalHeightCenter:
return CHANNEL_TOP_FRONT_CENTER;
case kAudioChannelLabel_VerticalHeightRight:
return CHANNEL_TOP_FRONT_RIGHT;
case kAudioChannelLabel_TopBackLeft:
return CHANNEL_TOP_BACK_LEFT;
case kAudioChannelLabel_TopBackCenter:
return CHANNEL_TOP_BACK_CENTER;
case kAudioChannelLabel_TopBackRight:
return CHANNEL_TOP_BACK_RIGHT;
default:
return CHANNEL_UNKNOWN;
}
}
AudioChannelLabel
cubeb_channel_to_channel_label(cubeb_channel channel)
{
switch (channel) {
case CHANNEL_FRONT_LEFT:
return kAudioChannelLabel_Left;
case CHANNEL_FRONT_RIGHT:
return kAudioChannelLabel_Right;
case CHANNEL_FRONT_CENTER:
return kAudioChannelLabel_Center;
case CHANNEL_LOW_FREQUENCY:
return kAudioChannelLabel_LFEScreen;
case CHANNEL_BACK_LEFT:
return kAudioChannelLabel_LeftSurround;
case CHANNEL_BACK_RIGHT:
return kAudioChannelLabel_RightSurround;
case CHANNEL_FRONT_LEFT_OF_CENTER:
return kAudioChannelLabel_LeftCenter;
case CHANNEL_FRONT_RIGHT_OF_CENTER:
return kAudioChannelLabel_RightCenter;
case CHANNEL_BACK_CENTER:
return kAudioChannelLabel_CenterSurround;
case CHANNEL_SIDE_LEFT:
return kAudioChannelLabel_LeftSurroundDirect;
case CHANNEL_SIDE_RIGHT:
return kAudioChannelLabel_RightSurroundDirect;
case CHANNEL_TOP_CENTER:
return kAudioChannelLabel_TopCenterSurround;
case CHANNEL_TOP_FRONT_LEFT:
return kAudioChannelLabel_VerticalHeightLeft;
case CHANNEL_TOP_FRONT_CENTER:
return kAudioChannelLabel_VerticalHeightCenter;
case CHANNEL_TOP_FRONT_RIGHT:
return kAudioChannelLabel_VerticalHeightRight;
case CHANNEL_TOP_BACK_LEFT:
return kAudioChannelLabel_TopBackLeft;
case CHANNEL_TOP_BACK_CENTER:
return kAudioChannelLabel_TopBackCenter;
case CHANNEL_TOP_BACK_RIGHT:
return kAudioChannelLabel_TopBackRight;
default:
return kAudioChannelLabel_Unknown;
}
}
bool
is_common_sample_rate(Float64 sample_rate)
{
/* Some commonly used sample rates and their multiples and divisors. */
return sample_rate == 8000 || sample_rate == 16000 || sample_rate == 22050 ||
sample_rate == 32000 || sample_rate == 44100 || sample_rate == 48000 ||
sample_rate == 88200 || sample_rate == 96000;
}
#if TARGET_OS_IPHONE
typedef UInt32 AudioDeviceID;
typedef UInt32 AudioObjectID;
#define AudioGetCurrentHostTime mach_absolute_time
#endif
uint64_t
ConvertHostTimeToNanos(uint64_t host_time)
{
static struct mach_timebase_info timebase_info;
static bool initialized = false;
if (!initialized) {
mach_timebase_info(&timebase_info);
initialized = true;
}
long double answer = host_time;
if (timebase_info.numer != timebase_info.denom) {
answer *= timebase_info.numer;
answer /= timebase_info.denom;
}
return (uint64_t)answer;
}
static void
audiounit_increment_active_streams(cubeb * ctx)
{
ctx->mutex.assert_current_thread_owns();
ctx->active_streams += 1;
}
static void
audiounit_decrement_active_streams(cubeb * ctx)
{
ctx->mutex.assert_current_thread_owns();
ctx->active_streams -= 1;
}
static int
audiounit_active_streams(cubeb * ctx)
{
ctx->mutex.assert_current_thread_owns();
return ctx->active_streams;
}
static void
audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames)
{
ctx->mutex.assert_current_thread_owns();
assert(audiounit_active_streams(ctx) == 1);
ctx->global_latency_frames = latency_frames;
}
static void
audiounit_make_silent(AudioBuffer * ioData)
{
assert(ioData);
assert(ioData->mData);
memset(ioData->mData, 0, ioData->mDataByteSize);
}
static OSStatus
audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags,
AudioTimeStamp const * tstamp, UInt32 bus,
UInt32 input_frames)
{
/* Create the AudioBufferList to store input. */
AudioBufferList input_buffer_list;
input_buffer_list.mBuffers[0].mDataByteSize =
stm->input_desc.mBytesPerFrame * input_frames;
input_buffer_list.mBuffers[0].mData = nullptr;
input_buffer_list.mBuffers[0].mNumberChannels =
stm->input_desc.mChannelsPerFrame;
input_buffer_list.mNumberBuffers = 1;
/* Render input samples */
OSStatus r = AudioUnitRender(stm->input_unit, flags, tstamp, bus,
input_frames, &input_buffer_list);
if (r != noErr) {
LOG("AudioUnitRender rv=%d", r);
if (r != kAudioUnitErr_CannotDoInCurrentContext) {
return r;
}
if (stm->output_unit) {
// kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT
// headset and the profile is changed from A2DP to HFP/HSP. The previous
// output device is no longer valid and must be reset.
audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);
}
// For now state that no error occurred and feed silence, stream will be
// resumed once reinit has completed.
ALOGV("(%p) input: reinit pending feeding silence instead", stm);
stm->input_linear_buffer->push_silence(input_frames *
stm->input_desc.mChannelsPerFrame);
} else {
/* Copy input data in linear buffer. */
stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
input_frames *
stm->input_desc.mChannelsPerFrame);
}
/* Advance input frame counter. */
assert(input_frames > 0);
stm->frames_read += input_frames;
ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, "
"total frames %lu.",
stm, (unsigned int)input_buffer_list.mNumberBuffers,
(unsigned int)input_buffer_list.mBuffers[0].mDataByteSize,
(unsigned int)input_buffer_list.mBuffers[0].mNumberChannels,
(unsigned int)input_frames,
stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
return noErr;
}
static OSStatus
audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
AudioTimeStamp const * tstamp, UInt32 bus,
UInt32 input_frames, AudioBufferList * /* bufs */)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
assert(stm->input_unit != NULL);
assert(AU_IN_BUS == bus);
if (stm->shutdown) {
ALOG("(%p) input shutdown", stm);
return noErr;
}
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
// Only fire state callback in input-only stream. For duplex stream,
// the state callback will be fired in output callback.
if (stm->output_unit == NULL) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
}
return noErr;
}
OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
if (r != noErr) {
return r;
}
// Full Duplex. We'll call data_callback in the AudioUnit output callback.
if (stm->output_unit != NULL) {
return noErr;
}
/* Input only. Call the user callback through resampler.
Resampler will deliver input buffer in the correct rate. */
assert(input_frames <= stm->input_linear_buffer->length() /
stm->input_desc.mChannelsPerFrame);
long total_input_frames =
stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
long outframes = cubeb_resampler_fill(stm->resampler.get(),
stm->input_linear_buffer->data(),
&total_input_frames, NULL, 0);
if (outframes < 0) {
stm->shutdown = true;
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return noErr;
}
stm->draining = outframes < total_input_frames;
// Reset input buffer
stm->input_linear_buffer->clear();
return noErr;
}
static void
audiounit_mix_output_buffer(cubeb_stream * stm, size_t output_frames,
void * input_buffer, size_t input_buffer_size,
void * output_buffer, size_t output_buffer_size)
{
assert(input_buffer_size >=
cubeb_sample_size(stm->output_stream_params.format) *
stm->output_stream_params.channels * output_frames);
assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames);
int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer,
input_buffer_size, output_buffer, output_buffer_size);
if (r != 0) {
LOG("Remix error = %d", r);
}
}
// Return how many input frames (sampled at input_hw_rate) are needed to provide
// output_frames (sampled at output_stream_params.rate)
static int64_t
minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)
{
if (stm->input_hw_rate == stm->output_stream_params.rate) {
// Fast path.
return output_frames;
}
return ceil(stm->input_hw_rate * output_frames /
stm->output_stream_params.rate);
}
static OSStatus
audiounit_output_callback(void * user_ptr,
AudioUnitRenderActionFlags * /* flags */,
AudioTimeStamp const * tstamp, UInt32 bus,
UInt32 output_frames, AudioBufferList * outBufferList)
{
assert(AU_OUT_BUS == bus);
assert(outBufferList->mNumberBuffers == 1);
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
uint64_t now = ConvertHostTimeToNanos(mach_absolute_time());
uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime);
uint64_t output_latency_ns = audio_output_time - now;
const int ns2s = 1e9;
// The total output latency is the timestamp difference + the stream latency +
// the hardware latency.
stm->total_output_latency_frames =
output_latency_ns * stm->output_hw_rate / ns2s +
stm->current_latency_frames;
ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input "
"frames %lu.",
stm, (unsigned int)outBufferList->mNumberBuffers,
(unsigned int)outBufferList->mBuffers[0].mDataByteSize,
(unsigned int)outBufferList->mBuffers[0].mNumberChannels,
(unsigned int)output_frames,
has_input(stm) ? stm->input_linear_buffer->length() /
stm->input_desc.mChannelsPerFrame
: 0);
long input_frames = 0;
void *output_buffer = NULL, *input_buffer = NULL;
if (stm->shutdown) {
ALOG("(%p) output shutdown.", stm);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
/* Get output buffer. */
if (stm->mixer) {
// If remixing needs to occur, we can't directly work in our final
// destination buffer as data may be overwritten or too small to start with.
size_t size_needed = output_frames * stm->output_stream_params.channels *
cubeb_sample_size(stm->output_stream_params.format);
if (stm->temp_buffer_size < size_needed) {
stm->temp_buffer.reset(new uint8_t[size_needed]);
stm->temp_buffer_size = size_needed;
}
output_buffer = stm->temp_buffer.get();
} else {
output_buffer = outBufferList->mBuffers[0].mData;
}
stm->frames_written += output_frames;
/* If Full duplex get also input buffer */
if (stm->input_unit != NULL) {
/* If the output callback came first and this is a duplex stream, we need to
* fill in some additional silence in the resampler.
* Otherwise, if we had more than expected callbacks in a row, or we're
* currently switching, we add some silence as well to compensate for the
* fact that we're lacking some input data. */
uint32_t input_frames_needed =
minimum_resampling_input_frames(stm, stm->frames_written);
long missing_frames = input_frames_needed - stm->frames_read;
if (missing_frames > 0) {
stm->input_linear_buffer->push_silence(missing_frames *
stm->input_desc.mChannelsPerFrame);
stm->frames_read = input_frames_needed;
ALOG("(%p) %s pushed %ld frames of input silence.", stm,
stm->frames_read == 0 ? "Input hasn't started,"
: stm->switching_device ? "Device switching,"
: "Drop out,",
missing_frames);
}
input_buffer = stm->input_linear_buffer->data();
// Number of input frames in the buffer. It will change to actually used
// frames inside fill
input_frames =
stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
}
/* Call user callback through resampler. */
long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer,
input_buffer ? &input_frames : NULL,
output_buffer, output_frames);
if (input_buffer) {
// Pop from the buffer the frames used by the the resampler.
stm->input_linear_buffer->pop(input_frames *
stm->input_desc.mChannelsPerFrame);
}
if (outframes < 0 || outframes > output_frames) {
stm->shutdown = true;
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
if (stm->input_unit) {
r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
}
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
stm->draining = (UInt32)outframes < output_frames;
stm->frames_played = stm->frames_queued;
stm->frames_queued += outframes;
/* Post process output samples. */
if (stm->draining) {
/* Clear missing frames (silence) */
size_t channels = stm->output_stream_params.channels;
size_t missing_samples = (output_frames - outframes) * channels;
size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);
/* number of bytes that have been filled with valid audio by the callback.
*/
size_t audio_byte_count = outframes * channels * size_sample;
PodZero((uint8_t *)output_buffer + audio_byte_count,
missing_samples * size_sample);
}
/* Mixing */
if (stm->mixer) {
audiounit_mix_output_buffer(stm, output_frames, output_buffer,
stm->temp_buffer_size,
outBufferList->mBuffers[0].mData,
outBufferList->mBuffers[0].mDataByteSize);
}
return noErr;
}
extern "C" {
int
audiounit_init(cubeb ** context, char const * /* context_name */)
{
#if !TARGET_OS_IPHONE
cubeb_set_coreaudio_notification_runloop();
#endif
*context = new cubeb;
return CUBEB_OK;
}
}
static char const *
audiounit_get_backend_id(cubeb * /* ctx */)
{
return "audiounit";
}
#if !TARGET_OS_IPHONE
static int
audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
static int
audiounit_stream_set_volume(cubeb_stream * stm, float volume);
static int
audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
{
assert(stm);
device_info * info = nullptr;
cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
if (side == io_side::INPUT) {
info = &stm->input_device;
type = CUBEB_DEVICE_TYPE_INPUT;
} else if (side == io_side::OUTPUT) {
info = &stm->output_device;
type = CUBEB_DEVICE_TYPE_OUTPUT;
}
memset(info, 0, sizeof(device_info));
info->id = id;
if (side == io_side::INPUT) {
info->flags |= DEV_INPUT;
} else if (side == io_side::OUTPUT) {
info->flags |= DEV_OUTPUT;
}
AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
if (default_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
if (id == kAudioObjectUnknown) {
info->id = default_device_id;
info->flags |= DEV_SELECTED_DEFAULT;
}
if (info->id == default_device_id) {
info->flags |= DEV_SYSTEM_DEFAULT;
}
assert(info->id);
assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
!(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
return CUBEB_OK;
}
static int
audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
{
auto_lock context_lock(stm->context->mutex);
assert((flags & DEV_INPUT && stm->input_unit) ||
(flags & DEV_OUTPUT && stm->output_unit));
if (!stm->shutdown) {
audiounit_stream_stop_internal(stm);
}
int r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall all device change listeners.", stm);
}
{
auto_lock lock(stm->mutex);
float volume = 0.0;
int vol_rv = CUBEB_ERROR;
if (stm->output_unit) {
vol_rv = audiounit_stream_get_volume(stm, &volume);
}
audiounit_close_stream(stm);
/* Reinit occurs in one of the following case:
* - When the device is not alive any more
* - When the default system device change.
* - The bluetooth device changed from A2DP to/from HFP/HSP profile
* We first attempt to re-use the same device id, should that fail we will
* default to the (potentially new) default device. */
AudioDeviceID input_device =
flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown;
if (flags & DEV_INPUT) {
r = audiounit_set_device_info(stm, input_device, io_side::INPUT);
if (r != CUBEB_OK) {
LOG("(%p) Set input device info failed. This can happen when last "
"media device is unplugged",
stm);
return CUBEB_ERROR;
}
}
/* Always use the default output on reinit. This is not correct in every
* case but it is sufficient for Firefox and prevent reinit from reporting
* failures. It will change soon when reinit mechanism will be updated. */
r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT);
if (r != CUBEB_OK) {
LOG("(%p) Set output device info failed. This can happen when last media "
"device is unplugged",
stm);
return CUBEB_ERROR;
}
if (audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Stream reinit failed.", stm);
if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) {
// Attempt to re-use the same device-id failed, so attempt again with
// default input device.
audiounit_close_stream(stm);
if (audiounit_set_device_info(stm, kAudioObjectUnknown,
io_side::INPUT) != CUBEB_OK ||
audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Second stream reinit failed.", stm);
return CUBEB_ERROR;
}
}
}
if (vol_rv == CUBEB_OK) {
audiounit_stream_set_volume(stm, volume);
}
// If the stream was running, start it again.
if (!stm->shutdown) {
r = audiounit_stream_start_internal(stm);
if (r != CUBEB_OK) {
return CUBEB_ERROR;
}
}
}
return CUBEB_OK;
}
static void
audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)
{
if (std::atomic_exchange(&stm->reinit_pending, true)) {
// A reinit task is already pending, nothing more to do.
ALOG("(%p) re-init stream task already pending, cancelling request", stm);
return;
}
// Use a new thread, through the queue, to avoid deadlock when calling
// Get/SetProperties method from inside notify callback
dispatch_async(stm->context->serial_queue, ^() {
if (stm->destroy_pending) {
ALOG("(%p) stream pending destroy, cancelling reinit task", stm);
return;
}
if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) {
if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
LOG("(%p) Could not uninstall system changed callback", stm);
}
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
LOG("(%p) Could not reopen the stream after switching.", stm);
}
stm->switching_device = false;
stm->reinit_pending = false;
});
}
static char const *
event_addr_to_string(AudioObjectPropertySelector selector)
{
switch (selector) {
case kAudioHardwarePropertyDefaultOutputDevice:
return "kAudioHardwarePropertyDefaultOutputDevice";
case kAudioHardwarePropertyDefaultInputDevice:
return "kAudioHardwarePropertyDefaultInputDevice";
case kAudioDevicePropertyDeviceIsAlive:
return "kAudioDevicePropertyDeviceIsAlive";
case kAudioDevicePropertyDataSource:
return "kAudioDevicePropertyDataSource";
default:
return "Unknown";
}
}
static OSStatus
audiounit_property_listener_callback(
AudioObjectID id, UInt32 address_count,
const AudioObjectPropertyAddress * addresses, void * user)
{
cubeb_stream * stm = (cubeb_stream *)user;
if (stm->switching_device) {
LOG("Switching is already taking place. Skip Event %s for id=%d",
event_addr_to_string(addresses[0].mSelector), id);
return noErr;
}
stm->switching_device = true;
LOG("(%p) Audio device changed, %u events.", stm,
(unsigned int)address_count);
for (UInt32 i = 0; i < address_count; i++) {
switch (addresses[i].mSelector) {
case kAudioHardwarePropertyDefaultOutputDevice: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice "
"for id=%d",
(unsigned int)i, id);
} break;
case kAudioHardwarePropertyDefaultInputDevice: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice "
"for id=%d",
(unsigned int)i, id);
} break;
case kAudioDevicePropertyDeviceIsAlive: {
LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for "
"id=%d",
(unsigned int)i, id);
// If this is the default input device ignore the event,
// kAudioHardwarePropertyDefaultInputDevice will take care of the switch
if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
LOG("It's the default input device, ignore the event");
stm->switching_device = false;
return noErr;
}
} break;
case kAudioDevicePropertyDataSource: {
LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d",
(unsigned int)i, id);
} break;
default:
LOG("Event[%u] - mSelector == Unexpected Event id %d, return",
(unsigned int)i, addresses[i].mSelector);
stm->switching_device = false;
return noErr;
}
}
// Allow restart to choose the new default
device_flags_value switch_side = DEV_UNKNOWN;
if (has_input(stm)) {
switch_side |= DEV_INPUT;
}