MythTV master
audiooutputca.cpp
Go to the documentation of this file.
1/*****************************************************************************
2 * = NAME
3 * audiooutputca.cpp
4 *
5 * = DESCRIPTION
6 * Core Audio glue for Mac OS X.
7 *
8 * = REVISION
9 * $Id$
10 *
11 * = AUTHORS
12 * Jeremiah Morris, Andrew Kimpton, Nigel Pearson, Jean-Yves Avenard
13 *****************************************************************************/
14
15#include <array>
16#include <cmath>
17#include <thread>
18#include <vector>
19
20#include <CoreServices/CoreServices.h>
21#include <CoreAudio/CoreAudio.h>
22#include <AudioUnit/AudioUnit.h>
23#include <AudioToolbox/AudioFormat.h>
24#include <AvailabilityMacros.h>
25
28#include "audiooutputca.h"
29
30// kAudioObjectPropertyElementMaster was deprecated in OS_X 12
31// kAudioObjectPropertyElementMain defaults to a main/master port value of 0
32static constexpr int8_t kMythAudioObjectPropertyElementMain { 0 };
33
34#define LOC QString("CoreAudio: ")
35
36static constexpr int CHANNELS_MIN { 1 };
37static constexpr int CHANNELS_MAX { 8 };
38
39using AudioStreamIDVec = std::vector<AudioStreamID>;
40using AudioStreamRangedVec = std::vector<AudioStreamRangedDescription>;
41using AudioValueRangeVec = std::vector<AudioValueRange>;
42using RatesVec = std::vector<int>;
43using ChannelsArr = std::array<bool,CHANNELS_MAX>;
44
45#define OSS_STATUS(x) UInt32ToFourCC((UInt32*)&(x))
46char* UInt32ToFourCC(const UInt32* pVal)
47{
48 UInt32 inVal = *pVal;
49 char* pIn = (char*)&inVal;
50 static std::array<char,5> fourCC;
51 fourCC[4] = 0;
52 fourCC[3] = pIn[0];
53 fourCC[2] = pIn[1];
54 fourCC[1] = pIn[2];
55 fourCC[0] = pIn[3];
56 return fourCC.data();
57}
58
59QString StreamDescriptionToString(AudioStreamBasicDescription desc)
60{
61 UInt32 formatId = desc.mFormatID;
62 char* fourCC = UInt32ToFourCC(&formatId);
63 QString str;
64
65 switch (desc.mFormatID)
66 {
67 case kAudioFormatLinearPCM:
68 str = QString("[%1] %2%3 Channel %4-bit %5 %6 (%7Hz) %8 Channels")
69 .arg(fourCC)
70 .arg((desc.mFormatFlags & kAudioFormatFlagIsNonMixable) ? "" : "Mixable ")
71 .arg(desc.mChannelsPerFrame)
72 .arg(desc.mBitsPerChannel)
73 .arg((desc.mFormatFlags & kAudioFormatFlagIsFloat) ? "Floating Point" : "Signed Integer")
74 .arg((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
75 .arg((UInt32)desc.mSampleRate)
76 .arg(desc.mChannelsPerFrame);
77 break;
78 case kAudioFormatAC3:
79 str = QString("[%1] AC-3/DTS (%2Hz) %3 Channels")
80 .arg(fourCC)
81 .arg((UInt32)desc.mSampleRate)
82 .arg(desc.mChannelsPerFrame);
83 break;
84 case kAudioFormat60958AC3:
85 str = QString("[%1] AC-3/DTS for S/PDIF %2 (%3Hz) %4 Channels")
86 .arg(fourCC)
87 .arg((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
88 .arg((UInt32)desc.mSampleRate)
89 .arg(desc.mChannelsPerFrame);
90 break;
91 default:
92 str = QString("[%1]").arg(fourCC);
93 break;
94 }
95 return str;
96}
97
103public:
104 explicit CoreAudioData(AudioOutputCA *parent);
105 CoreAudioData(AudioOutputCA *parent, AudioDeviceID deviceID);
106 CoreAudioData(AudioOutputCA *parent, const QString& deviceName);
107
108 static AudioDeviceID GetDefaultOutputDevice();
109 int GetTotalOutputChannels() const;
110 QString *GetName() const;
111 static AudioDeviceID GetDeviceWithName(const QString& deviceName);
112
114 int OpenAnalog();
115 void CloseAnalog();
116 bool OpenSPDIF ();
117 void CloseSPDIF();
118
119 static void SetAutoHogMode(bool enable);
120 static bool GetAutoHogMode();
121 static pid_t GetHogStatus();
122 bool SetHogStatus(bool hog);
123 bool SetMixingSupport(bool mix);
124 bool GetMixingSupport() const;
125
126 bool FindAC3Stream() const;
127 static void ResetAudioDevices();
128 static void ResetStream(AudioStreamID s);
129 static RatesVec RatesList(AudioDeviceID d);
130 bool ChannelsList(AudioDeviceID d, bool passthru, ChannelsArr& chans) const;
131
132 static AudioStreamIDVec StreamsList(AudioDeviceID d);
133 static AudioStreamRangedVec FormatsList(AudioStreamID s);
134
135 static int AudioStreamChangeFormat(AudioStreamID s,
136 AudioStreamBasicDescription format);
137
138 AudioOutputCA *mCA {nullptr}; // We could subclass, but this ends up tidier
139
140 // Analog output specific
141 AudioUnit mOutputUnit {nullptr};
142
143 // SPDIF mode specific
144 bool mDigitalInUse {false}; // Is the digital (SPDIF) output in use?
145 pid_t mHog {-1};
147 AudioDeviceID mDeviceID {0};
148 AudioStreamID mStreamID {}; // StreamID that has a cac3 streamformat
149 int mStreamIndex {-1}; // Index of mStreamID in an AudioBufferList
150 UInt32 mBytesPerPacket {UINT32_MAX};
151 AudioStreamBasicDescription mFormatOrig {}; // The original format the stream
152 AudioStreamBasicDescription mFormatNew {}; // The format we changed the stream to
153 bool mRevertFormat {false}; // Do we need to revert the stream format?
154 bool mIoProc {false};
155 bool mInitialized {false};
156 bool mStarted {false};
157 bool mWasDigital {false};
158 AudioDeviceIOProcID mIoProcID {};
159};
160
161// These callbacks communicate with Core Audio.
162static OSStatus RenderCallbackAnalog(void *inRefCon,
163 AudioUnitRenderActionFlags *ioActionFlags,
164 const AudioTimeStamp *inTimeStamp,
165 UInt32 inBusNumber,
166 UInt32 inNumberFrames,
167 AudioBufferList *ioData);
168static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice,
169 const AudioTimeStamp *inNow,
170 const void *inInputData,
171 const AudioTimeStamp *inInputTime,
172 AudioBufferList *outOutputData,
173 const AudioTimeStamp *inOutputTime,
174 void *inRefCon);
175
181AudioOutputBase(settings)
182{
183 m_mainDevice.remove(0, 10);
184 LOG(VB_AUDIO, LOG_INFO, LOC + QString("AudioOutputCA::AudioOutputCA searching %1").arg(m_mainDevice));
185 d = new CoreAudioData(this, m_mainDevice);
186
187 InitSettings(settings);
188 if (settings.m_init)
189 Reconfigure(settings);
190}
191
193{
194 KillAudio();
195
196 delete d;
197}
198
200{
201 auto *settings = new AudioOutputSettings();
202
203 // Seek hardware sample rate available
205
206 if (rates.empty())
207 {
208 // Error retrieving rates, assume 48kHz
209 settings->AddSupportedRate(48000);
210 }
211 else
212 {
213 while (int rate = settings->GetNextRate())
214 {
215 for (auto entry : rates)
216 {
217 if (entry != rate)
218 continue;
219 settings->AddSupportedRate(entry);
220 }
221 }
222 }
223
224 // Supported format: 16 bits audio or float
225 settings->AddSupportedFormat(FORMAT_S16);
226 settings->AddSupportedFormat(FORMAT_FLT);
227
229 if (!d->ChannelsList(d->mDeviceID, digital, channels))
230 {
231 // Error retrieving list of supported channels, assume stereo only
232 settings->AddSupportedChannels(2);
233 }
234 else
235 {
236 for (int i = CHANNELS_MIN; i <= CHANNELS_MAX; i++)
237 {
238 if (channels[i-1])
239 {
240 LOG(VB_AUDIO, LOG_INFO, QString("AudioOutputCA::Support %1 channels").arg(i));
241 // In case 8 channels are supported but not 6, fake 6
242 if (i == 8 && !channels[6-1])
243 settings->AddSupportedChannels(6);
244 settings->AddSupportedChannels(i);
245 }
246 }
247 }
248
249 if (d->FindAC3Stream())
250 {
251 settings->setPassthrough(1); // yes passthrough
252 }
253 return settings;
254}
255
257{
258 bool deviceOpened = false;
259
260 if (d->mWasDigital)
261 {
262 }
263 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice: Entering");
264 if (m_passthru || m_enc)
265 {
266 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice() Trying Digital.");
267 deviceOpened = d->OpenSPDIF();
268 if (!deviceOpened)
269 d->CloseSPDIF();
270 }
271
272 if (!deviceOpened)
273 {
274 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice() Trying Analog.");
275 int result = -1;
276 //for (int i=0; result < 0 && i < 10; i++)
277 {
278 result = d->OpenAnalog();
279 LOG(VB_AUDIO, LOG_INFO, QString("AudioOutputCA::OpenDevice: OpenAnalog = %1").arg(result));
280 if (result < 0)
281 {
282 d->CloseAnalog();
283 std::this_thread::sleep_for(1s);
284 }
285 }
286 deviceOpened = (result > 0);
287 }
288
289 if (!deviceOpened)
290 {
291 LOG(VB_GENERAL, LOG_ERR, "AudioOutputCA Error: Couldn't open any audio device!");
292 d->CloseAnalog();
293 return false;
294 }
295
297 {
298 QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
299 controlLabel += "MixerVolume";
300 SetCurrentVolume(gCoreContext->GetNumSetting(controlLabel, 80));
301 }
302
303 return true;
304}
305
307{
308 LOG(VB_AUDIO, LOG_INFO, LOC + QString("CloseDevice [%1]: Entering")
309 .arg(d->mDigitalInUse ? "SPDIF" : "Analog"));
310 if (d->mDigitalInUse)
311 d->CloseSPDIF();
312 else
313 d->CloseAnalog();
314}
315
316template <class AudioDataType>
317static inline void ReorderSmpteToCA(AudioDataType *buf, uint frames)
318{
319 AudioDataType tmpLS;
320 AudioDataType tmpRS;
321 AudioDataType tmpRLs;
322 AudioDataType tmpRRs;
323 AudioDataType *buf2 = nullptr;
324 for (uint i = 0; i < frames; i++)
325 {
326 buf = buf2 = buf + 4;
327 tmpRLs = *buf++;
328 tmpRRs = *buf++;
329 tmpLS = *buf++;
330 tmpRS = *buf++;
331
332 *buf2++ = tmpLS;
333 *buf2++ = tmpRS;
334 *buf2++ = tmpRLs;
335 *buf2++ = tmpRRs;
336 }
337}
338
339static inline void ReorderSmpteToCA(void *buf, uint frames, AudioFormat format)
340{
342 {
343 case 8: ReorderSmpteToCA((uchar *)buf, frames); break;
344 case 16: ReorderSmpteToCA((short *)buf, frames); break;
345 default: ReorderSmpteToCA((int *)buf, frames); break;
346 }
347}
348
350bool AudioOutputCA::RenderAudio(unsigned char *aubuf,
351 int size, unsigned long long timestamp)
352{
354 {
355 m_actuallyPaused = true;
356 return false;
357 }
358
359 /* This callback is called when the sound system requests
360 data. We don't want to block here, because that would
361 just cause dropouts anyway, so we always return whatever
362 data is available. If we haven't received enough, either
363 because we've finished playing or we have a buffer
364 underrun, we play silence to fill the unused space. */
365
366 int written_size = GetAudioData(aubuf, size, false);
367 if (written_size && (size > written_size))
368 {
369 // play silence on buffer underrun
370 memset(aubuf + written_size, 0, size - written_size);
371 }
372
373 //Audio received is in SMPTE channel order, reorder to CA unless passthru
374 if (!m_passthru && m_channels == 8)
375 {
377 }
378
379 /* update audiotime (m_bufferedBytes is read by GetBufferedOnSoundcard) */
380 UInt64 nanos = AudioConvertHostTimeToNanos(timestamp -
381 AudioGetCurrentHostTime());
382 m_bufferedBytes = (int)((nanos / 1000000000.0) * // secs
383 (m_effDsp / 100.0) * // frames/sec
384 m_outputBytesPerFrame); // bytes/frame
385
386 return (written_size > 0);
387}
388
389// unneeded and unused in CA
390void AudioOutputCA::WriteAudio([[maybe_unused]] unsigned char *aubuf,
391 [[maybe_unused]] int size)
392{
393}
394
396{
397 return m_bufferedBytes;
398}
399
403std::chrono::milliseconds AudioOutputCA::GetAudiotime(void)
404{
405 std::chrono::milliseconds audbuf_timecode = GetBaseAudBufTimeCode();
406
407 if (audbuf_timecode == 0ms)
408 return 0ms;
409
410 int totalbuffer = audioready() + GetBufferedOnSoundcard();
411
412 return audbuf_timecode - millisecondsFromFloat(totalbuffer * 100000.0 /
415}
416
417/* This callback provides converted audio data to the default output device. */
418OSStatus RenderCallbackAnalog(void *inRefCon,
419 AudioUnitRenderActionFlags *ioActionFlags,
420 const AudioTimeStamp *inTimeStamp,
421 [[maybe_unused]] UInt32 inBusNumber,
422 [[maybe_unused]] UInt32 inNumberFrames,
423 AudioBufferList *ioData)
424{
425 AudioOutputCA *inst = (static_cast<CoreAudioData *>(inRefCon))->mCA;
426
427 if (!inst->RenderAudio((unsigned char *)(ioData->mBuffers[0].mData),
428 ioData->mBuffers[0].mDataByteSize,
429 inTimeStamp->mHostTime))
430 {
431 // play silence if RenderAudio returns false
432 memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
433 *ioActionFlags = kAudioUnitRenderAction_OutputIsSilence;
434 }
435 return noErr;
436}
437
438int AudioOutputCA::GetVolumeChannel([[maybe_unused]] int channel) const
439{
440 // FIXME: this only returns global volume
441 Float32 volume = NAN;
442
443 if (!AudioUnitGetParameter(d->mOutputUnit,
444 kHALOutputParam_Volume,
445 kAudioUnitScope_Global, 0, &volume))
446 return (int)lroundf(volume * 100.0F);
447
448 return 0; // error case
449}
450
451void AudioOutputCA::SetVolumeChannel([[maybe_unused]] int channel, int volume)
452{
453 // FIXME: this only sets global volume
454 AudioUnitSetParameter(d->mOutputUnit, kHALOutputParam_Volume,
455 kAudioUnitScope_Global, 0, (volume * 0.01F), 0);
456}
457
458// IOProc style callback for SPDIF audio output
459static OSStatus RenderCallbackSPDIF([[maybe_unused]] AudioDeviceID inDevice,
460 [[maybe_unused]] const AudioTimeStamp *inNow,
461 [[maybe_unused]] const void *inInputData,
462 [[maybe_unused]] const AudioTimeStamp *inInputTime,
463 AudioBufferList *outOutputData,
464 const AudioTimeStamp *inOutputTime,
465 void *inRefCon)
466{
467 auto *d = static_cast<CoreAudioData *>(inRefCon);
468 AudioOutputCA *a = d->mCA;
469 int index = d->mStreamIndex;
470
471 /*
472 * HACK: No idea why this would be the case, but after the second run, we get
473 * incorrect value
474 */
475 if (d->mBytesPerPacket > 0 &&
476 outOutputData->mBuffers[index].mDataByteSize > d->mBytesPerPacket)
477 {
478 outOutputData->mBuffers[index].mDataByteSize = d->mBytesPerPacket;
479 }
480 if (!a->RenderAudio((unsigned char *)(outOutputData->mBuffers[index].mData),
481 outOutputData->mBuffers[index].mDataByteSize,
482 inOutputTime->mHostTime))
483 {
484 // play silence if RenderAudio returns false
485 memset(outOutputData->mBuffers[index].mData, 0,
486 outOutputData->mBuffers[index].mDataByteSize);
487 }
488 return noErr;
489}
490
492 mCA(parent), mDeviceID(GetDefaultOutputDevice())
493{
494 // Reset all the devices to a default 'non-hog' and mixable format.
495 // If we don't do this we may be unable to find the Default Output device.
496 // (e.g. if we crashed last time leaving it stuck in AC-3 mode)
498}
499
500CoreAudioData::CoreAudioData(AudioOutputCA *parent, AudioDeviceID deviceID) :
501 mCA(parent), mDeviceID(deviceID)
502{
504}
505
506CoreAudioData::CoreAudioData(AudioOutputCA *parent, const QString& deviceName) :
507 mCA(parent), mDeviceID(GetDeviceWithName(deviceName))
508{
510 if (!mDeviceID)
511 {
512 // Didn't find specified device, use default one
514 if (deviceName != "Default Output Device")
515 {
516 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:CoreAudioData: \"%1\" not found, using default device %2.")
517 .arg(deviceName).arg(mDeviceID));
518 }
519 }
520 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CoreAudioData: device number is %1")
521 .arg(mDeviceID));
522}
523
524AudioDeviceID CoreAudioData::GetDeviceWithName(const QString &deviceName)
525{
526 UInt32 size = 0;
527 AudioDeviceID deviceID = 0;
528 AudioObjectPropertyAddress pa
529 {
530 kAudioHardwarePropertyDevices,
531 kAudioObjectPropertyScopeGlobal,
533 };
534
535 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
536 0, nullptr, &size);
537 if (err)
538 {
539 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyDataSize: Unable to retrieve the property sizes. "
540 "Error [%1]")
541 .arg(err));
542 return deviceID;
543 }
544
545 UInt32 deviceCount = size / sizeof(AudioDeviceID);
546 std::vector<AudioDeviceID> devices = {};
547 devices.resize(deviceCount);
548
549 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
550 0, nullptr, &size, devices.data());
551 if (err)
552 {
553 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetDeviceWithName: Unable to retrieve the list of available devices. "
554 "Error [%1]")
555 .arg(err));
556 }
557 else
558 {
559 for (const auto & dev : devices)
560 {
561 CoreAudioData device(nullptr, dev);
562 if (device.GetTotalOutputChannels() == 0)
563 continue;
564 QString *name = device.GetName();
565 if (name && *name == deviceName)
566 {
567 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetDeviceWithName: Found: %1").arg(*name));
568 deviceID = dev;
569 delete name;
570 }
571 if (deviceID)
572 break;
573 }
574 }
575 return deviceID;
576}
577
579{
580 UInt32 paramSize = 0;
581 AudioDeviceID deviceId = 0;
582 AudioObjectPropertyAddress pa
583 {
584 kAudioHardwarePropertyDefaultOutputDevice,
585 kAudioObjectPropertyScopeGlobal,
587 };
588
589 // Find the ID of the default Device
590 paramSize = sizeof(deviceId);
591 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
592 0, nullptr, &paramSize, &deviceId);
593 if (err == noErr)
594 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetDefaultOutputDevice: default device ID = %1").arg(deviceId));
595 else
596 {
597 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetDefaultOutputDevice: could not get default audio device: [%1]")
598 .arg(OSS_STATUS(err)));
599 deviceId = 0;
600 }
601 return deviceId;
602}
603
605{
606 if (!mDeviceID)
607 return 0;
608 UInt32 channels = 0;
609 UInt32 size = 0;
610 AudioObjectPropertyAddress pa
611 {
612 kAudioDevicePropertyStreamConfiguration,
613 kAudioDevicePropertyScopeOutput, // Scope needs to be set to output to find output streams
615 };
616
617 OSStatus err = AudioObjectGetPropertyDataSize(mDeviceID, &pa,
618 0, nullptr, &size);
619 if (err)
620 {
621 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetTotalOutputChannels: Unable to get "
622 "size of device output channels - id: %1 Error = [%2]")
623 .arg(mDeviceID)
624 .arg(err));
625 return 0;
626 }
627
628 std::vector<char> plBuffer (size);
629 auto *pList = (AudioBufferList *)plBuffer.data();
630 err = AudioObjectGetPropertyData(mDeviceID, &pa,
631 0, nullptr, &size, pList);
632 if (!err)
633 {
634 for (UInt32 buffer = 0; buffer < pList->mNumberBuffers; buffer++)
635 channels += pList->mBuffers[buffer].mNumberChannels;
636 }
637 else
638 {
639 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetTotalOutputChannels: Unable to get "
640 "total device output channels - id: %1 Error = [%2]")
641 .arg(mDeviceID)
642 .arg(err));
643 }
644 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetTotalOutputChannels: Found %1 channels in %2 buffers")
645 .arg(channels).arg(pList->mNumberBuffers));
646 return channels;
647}
648
650{
651 if (!mDeviceID)
652 return nullptr;
653
654 AudioObjectPropertyAddress pa
655 {
656 kAudioObjectPropertyName,
657 kAudioObjectPropertyScopeGlobal,
659 };
660
661 CFStringRef name = nullptr;
662 UInt32 propertySize = sizeof(CFStringRef);
663 OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa,
664 0, nullptr, &propertySize, (void*)&name);
665 if (err)
666 {
667 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:AudioObjectGetPropertyData for kAudioObjectPropertyName error: [%1]")
668 .arg(err));
669 return nullptr;
670 }
671 char *cname = new char[CFStringGetLength(name) + 1];
672 CFStringGetCString(name, cname, CFStringGetLength(name) + 1, kCFStringEncodingUTF8);
673 auto *qname = new QString(cname);
674 delete[] cname;
675 return qname;
676}
677
679{
680 UInt32 val = 0;
681 UInt32 size = sizeof(val);
682 AudioObjectPropertyAddress pa
683 {
684 kAudioHardwarePropertyHogModeIsAllowed,
685 kAudioObjectPropertyScopeGlobal,
687 };
688
689 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr, &size, &val);
690 if (err)
691 {
692 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetAutoHogMode: Unable to get auto 'hog' mode. Error = [%1]")
693 .arg(err));
694 return false;
695 }
696 return (val == 1);
697}
698
700{
701 UInt32 val = enable ? 1 : 0;
702 AudioObjectPropertyAddress pa
703 {
704 kAudioHardwarePropertyHogModeIsAllowed,
705 kAudioObjectPropertyScopeGlobal,
707 };
708
709 OSStatus err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
710 sizeof(val), &val);
711 if (err)
712 {
713 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetAutoHogMode: Unable to set auto 'hog' mode. Error = [%1]")
714 .arg(err));
715 }
716}
717
719{
720 pid_t PID = 0;
721 UInt32 PIDsize = sizeof(PID);
722 AudioObjectPropertyAddress pa
723 {
724 kAudioDevicePropertyHogMode,
725 kAudioObjectPropertyScopeGlobal,
727 };
728
729 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
730 &PIDsize, &PID);
731 if (err != noErr)
732 {
733 // This is not a fatal error.
734 // Some drivers simply don't support this property
735 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetHogStatus: unable to check: [%1]")
736 .arg(err));
737 return -1;
738 }
739 return PID;
740}
741
743{
744 AudioObjectPropertyAddress pa
745 {
746 kAudioDevicePropertyHogMode,
747 kAudioObjectPropertyScopeGlobal,
749 };
750
751 // According to Jeff Moore (Core Audio, Apple), Setting kAudioDevicePropertyHogMode
752 // is a toggle and the only way to tell if you do get hog mode is to compare
753 // the returned pid against getpid, if the match, you have hog mode, if not you don't.
754 if (!mDeviceID)
755 return false;
756
757 if (hog)
758 {
759 if (mHog == -1) // Not already set
760 {
761 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Setting 'hog' status on device %1")
762 .arg(mDeviceID));
763 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
764 sizeof(mHog), &mHog);
765 if (err || mHog != getpid())
766 {
767 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetHogStatus: Unable to set 'hog' status. Error = [%1]")
768 .arg(OSS_STATUS(err)));
769 return false;
770 }
771 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Successfully set 'hog' status on device %1")
772 .arg(mDeviceID));
773 }
774 }
775 else
776 {
777 if (mHog > -1) // Currently Set
778 {
779 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Releasing 'hog' status on device %1")
780 .arg(mDeviceID));
781 pid_t hogPid = -1;
782 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
783 sizeof(hogPid), &hogPid);
784 if (err || hogPid == getpid())
785 {
786 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetHogStatus: Unable to release 'hog' status. Error = [%1]")
787 .arg(OSS_STATUS(err)));
788 return false;
789 }
790 mHog = hogPid; // Reset internal state
791 }
792 }
793 return true;
794}
795
797{
798 if (!mDeviceID)
799 return false;
800 int restore = -1;
801 if (mMixerRestore == -1) // This is our first change to this setting. Store the original setting for restore
802 restore = (GetMixingSupport() ? 1 : 0);
803 UInt32 mixEnable = mix ? 1 : 0;
804 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetMixingSupport: %1abling mixing for device %2")
805 .arg(mix ? "En" : "Dis")
806 .arg(mDeviceID));
807
808 AudioObjectPropertyAddress pa
809 {
810 kAudioDevicePropertySupportsMixing,
811 kAudioObjectPropertyScopeGlobal,
813 };
814 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
815 sizeof(mixEnable), &mixEnable);
816 if (err)
817 {
818 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetMixingSupport: Unable to set MixingSupport to %1. Error = [%2]")
819 .arg(mix ? "'On'" : "'Off'")
820 .arg(OSS_STATUS(err)));
821 return false;
822 }
823 if (mMixerRestore == -1)
824 mMixerRestore = restore;
825 return true;
826}
827
829{
830 if (!mDeviceID)
831 return false;
832 UInt32 val = 0;
833 UInt32 size = sizeof(val);
834 AudioObjectPropertyAddress pa
835 {
836 kAudioDevicePropertySupportsMixing,
837 kAudioObjectPropertyScopeGlobal,
839 };
840 OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa, 0, nullptr,
841 &size, &val);
842 if (err)
843 return false;
844 return (val > 0);
845}
846
851{
852 OSStatus err = 0;
853 UInt32 listSize = 0;
854 AudioStreamIDVec vec {};
855
856 AudioObjectPropertyAddress pa
857 {
858 kAudioDevicePropertyStreams,
859 kAudioObjectPropertyScopeGlobal,
861 };
862
863 err = AudioObjectGetPropertyDataSize(d, &pa,
864 0, nullptr, &listSize);
865 if (err != noErr)
866 {
867 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:StreamsList: could not get list size: [%1]")
868 .arg(OSS_STATUS(err)));
869 return {};
870 }
871 try
872 {
873 // Bugfix: vec.reserve will not change size of vector since contents are updated directly via memory copy
874 // In general all std::vector arrays have been changed from reserve to resize.
875 vec.resize(listSize / sizeof(AudioStreamID));
876 }
877 catch (...)
878 {
879 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:StreamsList(): out of memory?");
880 return {};
881 }
882
883 err = AudioObjectGetPropertyData(d, &pa,
884 0, nullptr, &listSize, vec.data());
885 if (err != noErr)
886 {
887 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:StreamsList: could not get list: [%1]")
888 .arg(OSS_STATUS(err)));
889 return {};
890 }
891
892 return vec;
893}
894
896{
897 OSStatus err = 0;
899 UInt32 listSize = 0;
900
901 AudioObjectPropertyAddress pa
902 {
903 // Bugfix: kAudioStreamPropertyPhysicalFormats, is meant to only give array of AudioStreamBasicDescription
904 kAudioStreamPropertyAvailablePhysicalFormats, // gives array of AudioStreamRangedDescription
905 kAudioObjectPropertyScopeGlobal,
907 };
908
909 // Retrieve all the stream formats supported by this output stream
910 err = AudioObjectGetPropertyDataSize(s, &pa, 0, nullptr, &listSize);
911 if (err != noErr)
912 {
913 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:FormatsList(): couldn't get list size: [%1]")
914 .arg(OSS_STATUS(err)));
915 return {};
916 }
917
918 try
919 {
920 vec.resize(listSize / sizeof(AudioStreamRangedDescription));
921 }
922 catch (...)
923 {
924 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:FormatsList(): out of memory?");
925 return {};
926 }
927
928 err = AudioObjectGetPropertyData(s, &pa, 0, nullptr, &listSize, vec.data());
929 if (err != noErr)
930 {
931 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:FormatsList: couldn't get list: [%1]")
932 .arg(OSS_STATUS(err)));
933 return {};
934 }
935
936 return vec;
937}
938
939static constexpr UInt32 sNumberCommonSampleRates = 15;
940static std::array<Float64,sNumberCommonSampleRates> sCommonSampleRates = {
941 8000.0, 11025.0, 12000.0,
942 16000.0, 22050.0, 24000.0,
943 32000.0, 44100.0, 48000.0,
944 64000.0, 88200.0, 96000.0,
945 128000.0, 176400.0, 192000.0 };
946
947static bool IsRateCommon(Float64 inRate)
948{
949 bool theAnswer = false;
950 for(UInt32 i = 0; !theAnswer && (i < sNumberCommonSampleRates); i++)
951 {
952 theAnswer = inRate == sCommonSampleRates[i];
953 }
954 return theAnswer;
955}
956
958{
959 OSStatus err = 0;
960 AudioValueRangeVec ranges;
961 RatesVec finalvec;
962 UInt32 listSize = 0;
963
964 AudioObjectPropertyAddress pa
965 {
966 kAudioDevicePropertyAvailableNominalSampleRates,
967 kAudioObjectPropertyScopeGlobal,
969 };
970
971 // retrieve size of rate list
972 err = AudioObjectGetPropertyDataSize(d, &pa, 0, nullptr, &listSize);
973 if (err != noErr)
974 {
975 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:RatesList(): couldn't get data rate list size: [%1]")
976 .arg(err));
977 return {};
978 }
979
980 try
981 {
982 ranges.resize(listSize / sizeof(AudioValueRange));
983 finalvec.reserve(listSize / sizeof(AudioValueRange));
984 }
985 catch (...)
986 {
987 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:RatesList(): out of memory?");
988 return {};
989 }
990
991 err = AudioObjectGetPropertyData(d, &pa, 0, nullptr, &listSize, ranges.data());
992 if (err != noErr)
993 {
994 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:RatesList(): couldn't get list: [%1]")
995 .arg(err));
996 return {};
997 }
998
999 // iterate through the ranges and add the minimum, maximum, and common rates in between
1000 UInt32 theFirstIndex = 0;
1001 UInt32 theLastIndex = 0;
1002 for(auto range : ranges)
1003 {
1004 theFirstIndex = theLastIndex;
1005 // find the index of the first common rate greater than or equal to the minimum
1006 while((theFirstIndex < sNumberCommonSampleRates) && (sCommonSampleRates[theFirstIndex] < range.mMinimum))
1007 theFirstIndex++;
1008
1009 if (theFirstIndex >= sNumberCommonSampleRates)
1010 break;
1011
1012 theLastIndex = theFirstIndex;
1013 // find the index of the first common rate greater than or equal to the maximum
1014 while((theLastIndex < sNumberCommonSampleRates) && (sCommonSampleRates[theLastIndex] < range.mMaximum))
1015 {
1016 finalvec.push_back(sCommonSampleRates[theLastIndex]);
1017 theLastIndex++;
1018 }
1019 if (IsRateCommon(range.mMinimum))
1020 finalvec.push_back(range.mMinimum);
1021 else if (IsRateCommon(range.mMaximum))
1022 finalvec.push_back(range.mMaximum);
1023 }
1024
1025 return finalvec;
1026}
1027
1028bool CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru, ChannelsArr& chans) const
1029{
1030 AudioStreamIDVec streams;
1032 bool founddigital = false;
1033
1034 chans.fill(false);
1035
1036 streams = StreamsList(mDeviceID);
1037 if (streams.empty())
1038 return false;
1039
1040 if (passthru)
1041 {
1042 for (auto stream : streams)
1043 {
1044 formats = FormatsList(stream);
1045 if (formats.empty())
1046 continue;
1047
1048 // Find a stream with a cac3 stream
1049 for (auto format : formats)
1050 {
1051 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ChannelsList: (passthru) found format: %1")
1052 .arg(StreamDescriptionToString(format.mFormat)));
1053 // Add supported number of channels
1054 if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
1055 chans[format.mFormat.mChannelsPerFrame-1] = true;
1056
1057 if (format.mFormat.mFormatID == 'IAC3' ||
1058 format.mFormat.mFormatID == kAudioFormat60958AC3)
1059 {
1060 // By default AC3 has 6 (5.1) channels but CoreAudio seems to set mChannelsPerFrame to 2
1061 // and considers it a "2 channel Encoded Digital Audio"
1062 chans[6-1] = true;
1063 // chans[format.mFormat.mChannelsPerFrame-1] = true;
1064 founddigital = true;
1065 }
1066 }
1067 }
1068 }
1069
1070 if (!founddigital)
1071 {
1072 for (auto stream : streams)
1073 {
1074 formats = FormatsList(stream);
1075 if (formats.empty())
1076 continue;
1077 for (auto format : formats)
1078 {
1079 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ChannelsList: (!founddigital) found format: %1")
1080 .arg(StreamDescriptionToString(format.mFormat)));
1081 if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
1082 chans[format.mFormat.mChannelsPerFrame-1] = true;
1083 }
1084 }
1085 }
1086 return true;
1087}
1088
1090{
1091 AudioComponentDescription desc;
1092 AudioStreamBasicDescription DeviceFormat;
1093 AudioChannelLayout new_layout;
1094 AudioDeviceID defaultDevice = GetDefaultOutputDevice();
1095 AudioObjectPropertyAddress pa
1096 {
1097 kAudioHardwarePropertyDevices,
1098 kAudioObjectPropertyScopeGlobal,
1100 };
1101
1102 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenAnalog: Entering");
1103
1104 desc.componentType = kAudioUnitType_Output;
1105 if (defaultDevice == mDeviceID)
1106 {
1107 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
1108 }
1109 else
1110 {
1111 desc.componentSubType = kAudioUnitSubType_HALOutput;
1112 }
1113 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
1114 desc.componentFlags = 0;
1115 desc.componentFlagsMask = 0;
1116 mDigitalInUse = false;
1117
1118 AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
1119 if (comp == nullptr)
1120 {
1121 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:OpenAnalog: AudioComponentFindNext failed");
1122 return false;
1123 }
1124
1125 OSErr err = AudioComponentInstanceNew(comp, &mOutputUnit);
1126 if (err)
1127 {
1128 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioComponentInstanceNew returned %1")
1129 .arg(err));
1130 return false;
1131 }
1132
1133 // Check if we have IO
1134 UInt32 hasIO = 0;
1135 UInt32 size_hasIO = sizeof(hasIO);
1136 err = AudioUnitGetProperty(mOutputUnit,
1137 kAudioOutputUnitProperty_HasIO,
1138 kAudioUnitScope_Output,
1139 0,
1140 &hasIO, &size_hasIO);
1141 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: HasIO (output) = %1").arg(hasIO));
1142 if (!hasIO)
1143 {
1144 UInt32 enableIO = 1;
1145 err = AudioUnitSetProperty(mOutputUnit,
1146 kAudioOutputUnitProperty_EnableIO,
1147 kAudioUnitScope_Global,
1148 0,
1149 &enableIO, sizeof(enableIO));
1150 if (err)
1151 {
1152 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: failed enabling IO: %1")
1153 .arg(err));
1154 }
1155 hasIO = 0;
1156 err = AudioUnitGetProperty(mOutputUnit,
1157 kAudioOutputUnitProperty_HasIO,
1158 kAudioUnitScope_Output,
1159 0,
1160 &hasIO, &size_hasIO);
1161 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::HasIO = %1").arg(hasIO));
1162 }
1163
1164 /*
1165 * We shouldn't have to do this distinction, however for some unknown reasons
1166 * assigning device to AudioUnit fail when switching from SPDIF mode
1167 */
1168 if (defaultDevice != mDeviceID)
1169 {
1170 err = AudioUnitSetProperty(mOutputUnit,
1171 kAudioOutputUnitProperty_CurrentDevice,
1172 kAudioUnitScope_Global,
1173 0,
1174 &mDeviceID, sizeof(mDeviceID));
1175 if (err)
1176 {
1177 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: Unable to set current device to %1. Error = %2")
1178 .arg(mDeviceID)
1179 .arg(err));
1180 return -1;
1181 }
1182 }
1183 /* Get the current format */
1184 UInt32 param_size = sizeof(AudioStreamBasicDescription);
1185
1186 err = AudioUnitGetProperty(mOutputUnit,
1187 kAudioUnitProperty_StreamFormat,
1188 kAudioUnitScope_Input,
1189 0,
1190 &DeviceFormat,
1191 &param_size );
1192 if (err)
1193 {
1194 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: Unable to retrieve current stream format: [%1]")
1195 .arg(err));
1196 }
1197 else
1198 {
1199 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: current format is: %1")
1200 .arg(StreamDescriptionToString(DeviceFormat)));
1201 }
1202 /* Get the channel layout of the device side of the unit */
1203 pa.mSelector = kAudioDevicePropertyPreferredChannelLayout;
1204 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1205 0, nullptr, &param_size);
1206
1207 if(!err)
1208 {
1209 std::vector<char> layoutBuffer (param_size);
1210 auto *layout = (AudioChannelLayout *)layoutBuffer.data();
1211 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1212 0, nullptr, &param_size, layout);
1213
1214 /* We need to "fill out" the ChannelLayout, because there are multiple ways that it can be set */
1215 if(layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
1216 {
1217 /* bitmap defined channellayout */
1218 err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
1219 sizeof(UInt32), &layout->mChannelBitmap,
1220 &param_size,
1221 layout);
1222 if (err)
1223 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenAnalog: Can't retrieve current channel layout");
1224 }
1225 else if(layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions )
1226 {
1227 /* layouttags defined channellayout */
1228 err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
1229 sizeof(AudioChannelLayoutTag),
1230 &layout->mChannelLayoutTag,
1231 &param_size,
1232 layout);
1233 if (err)
1234 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenAnalog: Can't retrieve current channel layout");
1235 }
1236
1237 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: Layout of AUHAL has %1 channels")
1238 .arg(layout->mNumberChannelDescriptions));
1239
1240 int channels_found = 0;
1241 for(UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++)
1242 {
1243 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: this is channel: %1")
1244 .arg(layout->mChannelDescriptions[i].mChannelLabel));
1245
1246 switch( layout->mChannelDescriptions[i].mChannelLabel)
1247 {
1248 case kAudioChannelLabel_Left:
1249 case kAudioChannelLabel_Right:
1250 case kAudioChannelLabel_Center:
1251 case kAudioChannelLabel_LFEScreen:
1252 case kAudioChannelLabel_LeftSurround:
1253 case kAudioChannelLabel_RightSurround:
1254 case kAudioChannelLabel_RearSurroundLeft:
1255 case kAudioChannelLabel_RearSurroundRight:
1256 case kAudioChannelLabel_CenterSurround:
1257 channels_found++;
1258 break;
1259 default:
1260 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::unrecognized channel form provided by driver: %1")
1261 .arg(layout->mChannelDescriptions[i].mChannelLabel));
1262 }
1263 }
1264 if(channels_found == 0)
1265 {
1266 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:Audio device is not configured. "
1267 "You should configure your speaker layout with "
1268 "the \"Audio Midi Setup\" utility in /Applications/"
1269 "Utilities.");
1270 }
1271 }
1272 else
1273 {
1274 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:this driver does not support kAudioDevicePropertyPreferredChannelLayout.");
1275 }
1276
1277 memset (&new_layout, 0, sizeof(new_layout));
1278 switch(mCA->m_channels)
1279 {
1280 case 1:
1281 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
1282 break;
1283 case 2:
1284 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
1285 break;
1286 case 6:
1287 // 3F2-LFE L R C LFE LS RS
1288 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_5_1;
1289 break;
1290 case 8:
1291 // We need
1292 // 3F4-LFE L R C LFE Rls Rrs LS RS
1293 // but doesn't exist, so we'll swap channels later
1294 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; // L R C LFE Ls Rs Lc Rc
1295 break;
1296 }
1297 /* Set new_layout as the layout */
1298 err = AudioUnitSetProperty(mOutputUnit,
1299 kAudioUnitProperty_AudioChannelLayout,
1300 kAudioUnitScope_Input,
1301 0,
1302 &new_layout, sizeof(new_layout));
1303 if (err)
1304 {
1305 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: couldn't set channels layout [%1]")
1306 .arg(err));
1307 }
1308
1309 if(new_layout.mNumberChannelDescriptions > 0)
1310 {
1311 // memory allocated by C library
1312 // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
1313 free(new_layout.mChannelDescriptions);
1314 }
1315
1316 // Set up the audio output unit
1317 int formatFlags = 0;
1318 switch (mCA->m_outputFormat)
1319 {
1320 case FORMAT_S16:
1321 formatFlags = kLinearPCMFormatFlagIsSignedInteger;
1322 break;
1323 case FORMAT_FLT:
1324 formatFlags = kLinearPCMFormatFlagIsFloat;
1325 break;
1326 default:
1327 formatFlags = kLinearPCMFormatFlagIsSignedInteger;
1328 break;
1329 }
1330
1331 AudioStreamBasicDescription conv_in_desc;
1332 memset(&conv_in_desc, 0, sizeof(AudioStreamBasicDescription));
1333 conv_in_desc.mSampleRate = mCA->m_sampleRate;
1334 conv_in_desc.mFormatID = kAudioFormatLinearPCM;
1335 conv_in_desc.mFormatFlags = formatFlags |
1336 kAudioFormatFlagsNativeEndian |
1337 kLinearPCMFormatFlagIsPacked;
1338 conv_in_desc.mBytesPerPacket = mCA->m_outputBytesPerFrame;
1339 // This seems inefficient, does it hurt if we increase this?
1340 conv_in_desc.mFramesPerPacket = 1;
1341 conv_in_desc.mBytesPerFrame = mCA->m_outputBytesPerFrame;
1342 conv_in_desc.mChannelsPerFrame = mCA->m_channels;
1343 conv_in_desc.mBitsPerChannel =
1345
1346 /* Set AudioUnit input format */
1347 err = AudioUnitSetProperty(mOutputUnit,
1348 kAudioUnitProperty_StreamFormat,
1349 kAudioUnitScope_Input,
1350 0,
1351 &conv_in_desc,
1352 sizeof(AudioStreamBasicDescription));
1353 if (err)
1354 {
1355 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioUnitSetProperty returned [%1]")
1356 .arg(err));
1357 return false;
1358 }
1359 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: set format as %1")
1360 .arg(StreamDescriptionToString(conv_in_desc)));
1361 /* Retrieve actual format */
1362 err = AudioUnitGetProperty(mOutputUnit,
1363 kAudioUnitProperty_StreamFormat,
1364 kAudioUnitScope_Input,
1365 0,
1366 &DeviceFormat,
1367 &param_size);
1368
1369 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: the actual set AU format is %1")
1370 .arg(StreamDescriptionToString(DeviceFormat)));
1371
1372 // Attach callback to default output
1373 AURenderCallbackStruct input;
1374 input.inputProc = RenderCallbackAnalog;
1375 input.inputProcRefCon = this;
1376
1377 err = AudioUnitSetProperty(mOutputUnit,
1378 kAudioUnitProperty_SetRenderCallback,
1379 kAudioUnitScope_Input,
1380 0, &input, sizeof(input));
1381 if (err)
1382 {
1383 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioUnitSetProperty (callback) returned [%1]")
1384 .arg(err));
1385 return false;
1386 }
1387 mIoProc = true;
1388
1389 // We're all set up - start the audio output unit
1390 ComponentResult res = AudioUnitInitialize(mOutputUnit);
1391 if (res)
1392 {
1393 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioUnitInitialize error: [%1]")
1394 .arg(res));
1395 return false;
1396 }
1397 mInitialized = true;
1398
1399 err = AudioOutputUnitStart(mOutputUnit);
1400 if (err)
1401 {
1402 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioOutputUnitStart error: [%1]")
1403 .arg(err));
1404 return false;
1405 }
1406 mStarted = true;
1407 return true;
1408}
1409
1411{
1412 OSStatus err = 0;
1413
1414 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: Entering: %1")
1415 .arg((long)mOutputUnit));
1416 if (mOutputUnit)
1417 {
1418 if (mStarted)
1419 {
1420 err = AudioOutputUnitStop(mOutputUnit);
1421 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: AudioOutputUnitStop %1")
1422 .arg(err));
1423 }
1424 if (mInitialized)
1425 {
1426 err = AudioUnitUninitialize(mOutputUnit);
1427 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: AudioUnitUninitialize %1")
1428 .arg(err));
1429 }
1430 err = AudioComponentInstanceDispose(mOutputUnit);
1431 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: CloseComponent %1")
1432 .arg(err));
1433 mOutputUnit = nullptr;
1434 }
1435 mIoProc = false;
1436 mInitialized = false;
1437 mStarted = false;
1438 mWasDigital = false;
1439}
1440
1442{
1443 OSStatus err = 0;
1444 AudioStreamIDVec streams;
1445 AudioStreamBasicDescription outputFormat {};
1446
1447 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenSPDIF: Entering");
1448
1449 streams = StreamsList(mDeviceID);
1450 if (streams.empty())
1451 {
1452 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenSPDIF: Couldn't retrieve list of streams");
1453 return false;
1454 }
1455
1456 for (size_t i = 0; i < streams.size(); ++i)
1457 {
1459 if (formats.empty())
1460 continue;
1461
1462 // Find a stream with a cac3 stream
1463 for (auto format : formats)
1464 {
1465 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenSPDIF: Considering Physical Format: %1")
1466 .arg(StreamDescriptionToString(format.mFormat)));
1467 if ((format.mFormat.mFormatID == 'IAC3' ||
1468 format.mFormat.mFormatID == kAudioFormat60958AC3) &&
1469 format.mFormat.mSampleRate == mCA->m_sampleRate)
1470 {
1471 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenSPDIF: Found digital format");
1472 mStreamIndex = i;
1473 mStreamID = streams[i];
1474 outputFormat = format.mFormat;
1475 break;
1476 }
1477 }
1478 if (outputFormat.mFormatID)
1479 break;
1480 }
1481
1482 if (!outputFormat.mFormatID)
1483 {
1484 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenSPDIF: Couldn't find suitable output"));
1485 return false;
1486 }
1487
1488 if (!mRevertFormat)
1489 {
1490 AudioObjectPropertyAddress pa
1491 {
1492 kAudioStreamPropertyPhysicalFormat,
1493 kAudioObjectPropertyScopeGlobal,
1495 };
1496
1497 // Retrieve the original format of this stream first
1498 // if not done so already
1499 UInt32 paramSize = sizeof(mFormatOrig);
1500 err = AudioObjectGetPropertyData(mStreamID, &pa, 0, nullptr,
1501 &paramSize, &mFormatOrig);
1502 if (err != noErr)
1503 {
1504 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenSPDIF - could not retrieve the original streamformat: [%1]")
1505 .arg(OSS_STATUS(err)));
1506 }
1507 else
1508 {
1509 mRevertFormat = true;
1510 }
1511 }
1512
1513 mDigitalInUse = true;
1514
1515 SetAutoHogMode(false);
1516 bool autoHog = GetAutoHogMode();
1517 if (!autoHog)
1518 {
1519 // Hog the device
1520 SetHogStatus(true);
1521 // Set mixable to false if we are allowed to
1522 SetMixingSupport(false);
1523 }
1524
1525 mFormatNew = outputFormat;
1527 {
1528 return false;
1529 }
1530 mBytesPerPacket = mFormatNew.mBytesPerPacket;
1531
1532 // Add IOProc callback
1533 err = AudioDeviceCreateIOProcID(mDeviceID,
1534 (AudioDeviceIOProc)RenderCallbackSPDIF,
1535 (void *)this, &mIoProcID);
1536 if (err != noErr)
1537 {
1538 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenSPDIF: AudioDeviceCreateIOProcID failed: [%1]")
1539 .arg(OSS_STATUS(err)));
1540 return false;
1541 }
1542 mIoProc = true;
1543
1544 // Start device
1545 err = AudioDeviceStart(mDeviceID, mIoProcID);
1546 if (err != noErr)
1547 {
1548 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenSPDIF: AudioDeviceStart failed: [%1]")
1549 .arg(OSS_STATUS(err)));
1550 return false;
1551 }
1552 mStarted = true;
1553 return true;
1554}
1555
1557{
1558 OSStatus err = 0;
1559
1560 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseSPDIF: Entering [%1]").arg(mDigitalInUse));;
1561 if (!mDigitalInUse)
1562 return;
1563
1564 // Stop device
1565 if (mStarted)
1566 {
1567 err = AudioDeviceStop(mDeviceID, mIoProcID);
1568 if (err != noErr)
1569 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:CloseSPDIF: AudioDeviceStop failed: [%1]")
1570 .arg(OSS_STATUS(err)));
1571 mStarted = false;
1572 }
1573
1574 // Remove IOProc callback
1575 if (mIoProc)
1576 {
1577 err = AudioDeviceDestroyIOProcID(mDeviceID, mIoProcID);
1578 if (err != noErr)
1579 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:CloseSPDIF: AudioDeviceDestroyIOProcID failed: [%1]")
1580 .arg(OSS_STATUS(err)));
1581 mIoProc = false;
1582 }
1583
1584 if (mRevertFormat)
1585 {
1587 mRevertFormat = false;
1588 }
1589
1590 SetHogStatus(false);
1591 if (mMixerRestore > -1) // We changed the mixer status
1593 AudioHardwareUnload();
1594 mMixerRestore = -1;
1595 mBytesPerPacket = -1;
1596 mStreamIndex = -1;
1597 mWasDigital = true;
1598}
1599
1601 AudioStreamBasicDescription format)
1602{
1603 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::AudioStreamChangeFormat: %1 -> %2")
1604 .arg(s)
1605 .arg(StreamDescriptionToString(format)));
1606
1607 AudioObjectPropertyAddress pa
1608 {
1609 kAudioStreamPropertyPhysicalFormat,
1610 kAudioObjectPropertyScopeGlobal,
1612 };
1613 OSStatus err = AudioObjectSetPropertyData(s, &pa, 0, nullptr,
1614 sizeof(format), &format);
1615 if (err != noErr)
1616 {
1617 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:AudioStreamChangeFormat couldn't set stream format: [%1]")
1618 .arg(OSS_STATUS(err)));
1619 return false;
1620 }
1621 return true;
1622}
1623
1625{
1626 AudioStreamIDVec streams;
1627
1628
1629 // Get a list of all the streams on this device
1630 streams = StreamsList(mDeviceID);
1631 if (streams.empty())
1632 return false;
1633
1634 for (auto stream : streams)
1635 {
1637 if (formats.empty())
1638 continue;
1639
1640 // Find a stream with a cac3 stream
1641 for (auto format : formats) {
1642 if (format.mFormat.mFormatID == 'IAC3' ||
1643 format.mFormat.mFormatID == kAudioFormat60958AC3)
1644 {
1645 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::FindAC3Stream: found digital format");
1646 return true;
1647 }
1648 }
1649 }
1650
1651 return false;
1652}
1653
1659{
1660 UInt32 size = 0;
1661 AudioObjectPropertyAddress pa
1662 {
1663 kAudioHardwarePropertyDevices,
1664 kAudioObjectPropertyScopeGlobal,
1666 };
1667
1668 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1669 0, nullptr, &size);
1670 if (err)
1671 {
1672 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyDataSize: Unable to retrieve the property sizes. "
1673 "Error [%1]")
1674 .arg(err));
1675 return;
1676 }
1677
1678 std::vector<AudioDeviceID> devices = {};
1679 devices.resize(size / sizeof(AudioDeviceID));
1680 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1681 0, nullptr, &size, devices.data());
1682 if (err)
1683 {
1684 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyData: Unable to retrieve the list of available devices. "
1685 "Error [%1]")
1686 .arg(err));
1687 return;
1688 }
1689
1690 for (const auto & dev : devices)
1691 {
1692 AudioStreamIDVec streams;
1693
1694 streams = StreamsList(dev);
1695 if (streams.empty())
1696 continue;
1697 for (auto stream : streams)
1698 ResetStream(stream);
1699 }
1700}
1701
1702void CoreAudioData::ResetStream(AudioStreamID s)
1703{
1704 AudioStreamBasicDescription currentFormat;
1705 OSStatus err = 0;
1706 UInt32 paramSize = 0;
1707 AudioObjectPropertyAddress pa
1708 {
1709 kAudioStreamPropertyPhysicalFormat,
1710 kAudioObjectPropertyScopeGlobal,
1712 };
1713
1714
1715 // Find the streams current physical format
1716 paramSize = sizeof(currentFormat);
1717 AudioObjectGetPropertyData(s, &pa, 0, nullptr,
1718 &paramSize, &currentFormat);
1719
1720 // If it's currently AC-3/SPDIF then reset it to some mixable format
1721 if (currentFormat.mFormatID == 'IAC3' ||
1722 currentFormat.mFormatID == kAudioFormat60958AC3)
1723 {
1725
1726
1727 if (formats.empty())
1728 return;
1729
1730 for (auto format : formats) {
1731 if (format.mFormat.mFormatID == kAudioFormatLinearPCM)
1732 {
1733 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ResetStream: Resetting stream %1 to %2").arg(s).arg(StreamDescriptionToString(format.mFormat)));
1734 err = AudioObjectSetPropertyData(s, &pa, 0, nullptr,
1735 sizeof(format), &(format.mFormat));
1736 if (err != noErr)
1737 {
1738 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:ResetStream: could not set physical format: [%1]")
1739 .arg(OSS_STATUS(err)));
1740 continue;
1741 }
1742
1743 std::this_thread::sleep_for(1s); // For the change to take effect
1744 }
1745 }
1746 }
1747}
1748
1749QMap<QString, QString> *AudioOutputCA::GetDevices(const char */*type*/)
1750{
1751 auto *devs = new QMap<QString, QString>();
1752
1753 // Obtain a list of all available audio devices
1754 UInt32 size = 0;
1755
1756 AudioObjectPropertyAddress pa
1757 {
1758 kAudioHardwarePropertyDevices,
1759 kAudioObjectPropertyScopeGlobal,
1761 };
1762
1763 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1764 0, nullptr, &size);
1765 if (err)
1766 {
1767 LOG(VB_AUDIO, LOG_INFO, LOC + QString("GetPropertyDataSize: Unable to retrieve the property sizes. "
1768 "Error [%1]")
1769 .arg(err));
1770 return devs;
1771 }
1772
1773 UInt32 deviceCount = size / sizeof(AudioDeviceID);
1774 std::vector<AudioDeviceID> devices = {};
1775 devices.resize(deviceCount);
1776 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1777 0, nullptr, &size, devices.data());
1778 if (err)
1779 {
1780 LOG(VB_AUDIO, LOG_INFO, LOC + QString("AudioOutputCA::GetDevices: Unable to retrieve the list of "
1781 "available devices. Error [%1]")
1782 .arg(err));
1783 }
1784 else
1785 {
1786 LOG(VB_AUDIO, LOG_INFO, LOC + QString("GetDevices: Number of devices: %1").arg(deviceCount));
1787
1788 for (const auto & dev : devices)
1789 {
1790 CoreAudioData device(nullptr, dev);
1791 if (device.GetTotalOutputChannels() == 0)
1792 continue;
1793 QString *name = device.GetName();
1794 if (name)
1795 {
1796 devs->insert(*name, QString());
1797 delete name;
1798 }
1799 }
1800 }
1801 return devs;
1802}
std::vector< AudioStreamID > AudioStreamIDVec
std::vector< AudioValueRange > AudioValueRangeVec
#define LOC
std::vector< AudioStreamRangedDescription > AudioStreamRangedVec
QString StreamDescriptionToString(AudioStreamBasicDescription desc)
static OSStatus RenderCallbackAnalog(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice, const AudioTimeStamp *inNow, const void *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inRefCon)
#define OSS_STATUS(x)
static bool IsRateCommon(Float64 inRate)
char * UInt32ToFourCC(const UInt32 *pVal)
std::vector< int > RatesVec
static constexpr UInt32 sNumberCommonSampleRates
static std::array< Float64, sNumberCommonSampleRates > sCommonSampleRates
static constexpr int8_t kMythAudioObjectPropertyElementMain
std::array< bool, CHANNELS_MAX > ChannelsArr
static constexpr int CHANNELS_MAX
static constexpr int CHANNELS_MIN
static void ReorderSmpteToCA(AudioDataType *buf, uint frames)
@ FORMAT_FLT
@ FORMAT_S16
static const std::array< const std::string, 8 > formats
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
void InitSettings(const AudioSettings &settings)
std::chrono::milliseconds GetBaseAudBufTimeCode() const
int GetAudioData(uchar *buffer, int buf_size, bool full_buffer, volatile uint *local_raud=nullptr)
Copy frames from the audiobuffer into the buffer provided.
int audioready() const
Get the scaled number of bytes in the audiobuffer, i.e.
Implements Core Audio (Mac OS X Hardware Abstraction Layer) output.
Definition: audiooutputca.h:14
friend class CoreAudioData
Definition: audiooutputca.h:46
std::chrono::milliseconds GetAudiotime(void) override
Reimplement the base class's version of GetAudiotime() so that we don't use gettimeofday or Qt mutexe...
int GetVolumeChannel(int channel) const override
void CloseDevice(void) override
virtual ~AudioOutputCA()
void SetVolumeChannel(int channel, int volume) override
void WriteAudio(unsigned char *aubuf, int size) override
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
AudioOutputSettings * GetOutputSettings(bool digital) override
static QMap< QString, QString > * GetDevices(const char *type=nullptr)
CoreAudioData * d
Definition: audiooutputca.h:45
AudioOutputCA(const AudioSettings &settings)
bool OpenDevice(void) override
bool RenderAudio(unsigned char *aubuf, int size, unsigned long long timestamp)
Object-oriented part of callback.
static int FormatToBits(AudioFormat format)
const int & channels() const
Definition: audiooutput.h:254
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
This holds Core Audio member variables and low-level audio IO methods The name is now a misnomer,...
static void SetAutoHogMode(bool enable)
static AudioDeviceID GetDeviceWithName(const QString &deviceName)
AudioStreamBasicDescription mFormatOrig
static AudioStreamRangedVec FormatsList(AudioStreamID s)
static AudioDeviceID GetDefaultOutputDevice()
AudioDeviceIOProcID mIoProcID
static pid_t GetHogStatus()
static RatesVec RatesList(AudioDeviceID d)
static AudioStreamIDVec StreamsList(AudioDeviceID d)
Get a list of all the streams on this device.
QString * GetName() const
AudioDeviceID mDeviceID
int GetTotalOutputChannels() const
UInt32 mBytesPerPacket
static int AudioStreamChangeFormat(AudioStreamID s, AudioStreamBasicDescription format)
AudioUnit mOutputUnit
bool ChannelsList(AudioDeviceID d, bool passthru, ChannelsArr &chans) const
bool FindAC3Stream() const
static bool GetAutoHogMode()
bool SetHogStatus(bool hog)
bool SetMixingSupport(bool mix)
bool OpenDevice()
AudioStreamBasicDescription mFormatNew
CoreAudioData(AudioOutputCA *parent)
static void ResetStream(AudioStreamID s)
AudioStreamID mStreamID
AudioOutputCA * mCA
static void ResetAudioDevices()
Reset any devices with an AC3 stream back to a Linear PCM so that they can become a default output de...
bool GetMixingSupport() const
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
Contains Packet Identifier numeric values.
Definition: mpegtables.h:207
bool m_internalVol
Definition: volumebase.h:43
virtual void SetCurrentVolume(int value)
Definition: volumebase.cpp:124
unsigned int uint
Definition: compat.h:60
static const iso6937table * d
std::chrono::milliseconds millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:79
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39