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 <SoundTouch.h>
16
17#include <array>
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
36#define CHANNELS_MIN 1
37#define 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 char fourCC[5];
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;
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, QString deviceName);
107
108 static AudioDeviceID GetDefaultOutputDevice();
110 QString *GetName();
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();
125
126 bool FindAC3Stream();
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);
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
228 ChannelsArr channels {};
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 if (!(deviceOpened = d->OpenSPDIF()))
268 d->CloseSPDIF();
269 }
270
271 if (!deviceOpened)
272 {
273 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice() Trying Analog.");
274 int result = -1;
275 //for (int i=0; result < 0 && i < 10; i++)
276 {
277 result = d->OpenAnalog();
278 LOG(VB_AUDIO, LOG_INFO, QString("AudioOutputCA::OpenDevice: OpenAnalog = %1").arg(result));
279 if (result < 0)
280 {
281 d->CloseAnalog();
282 usleep(1s - 1us); // Argument to usleep must be less than 1 second
283 }
284 }
285 deviceOpened = (result > 0);
286 }
287
288 if (!deviceOpened)
289 {
290 LOG(VB_GENERAL, LOG_ERR, "AudioOutputCA Error: Couldn't open any audio device!");
291 d->CloseAnalog();
292 return false;
293 }
294
296 {
297 QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
298 controlLabel += "MixerVolume";
299 SetCurrentVolume(gCoreContext->GetNumSetting(controlLabel, 80));
300 }
301
302 return true;
303}
304
306{
307 LOG(VB_AUDIO, LOG_INFO, LOC + QString("CloseDevice [%1]: Entering")
308 .arg(d->mDigitalInUse ? "SPDIF" : "Analog"));
309 if (d->mDigitalInUse)
310 d->CloseSPDIF();
311 else
312 d->CloseAnalog();
313}
314
315template <class AudioDataType>
316static inline void _ReorderSmpteToCA(AudioDataType *buf, uint frames)
317{
318 AudioDataType tmpLS;
319 AudioDataType tmpRS;
320 AudioDataType tmpRLs;
321 AudioDataType tmpRRs;
322 AudioDataType *buf2;
323 for (uint i = 0; i < frames; i++)
324 {
325 buf = buf2 = buf + 4;
326 tmpRLs = *buf++;
327 tmpRRs = *buf++;
328 tmpLS = *buf++;
329 tmpRS = *buf++;
330
331 *buf2++ = tmpLS;
332 *buf2++ = tmpRS;
333 *buf2++ = tmpRLs;
334 *buf2++ = tmpRRs;
335 }
336}
337
338static inline void ReorderSmpteToCA(void *buf, uint frames, AudioFormat format)
339{
341 {
342 case 8: _ReorderSmpteToCA((uchar *)buf, frames); break;
343 case 16: _ReorderSmpteToCA((short *)buf, frames); break;
344 default: _ReorderSmpteToCA((int *)buf, frames); break;
345 }
346}
347
349bool AudioOutputCA::RenderAudio(unsigned char *aubuf,
350 int size, unsigned long long timestamp)
351{
353 {
354 m_actuallyPaused = true;
355 return false;
356 }
357
358 /* This callback is called when the sound system requests
359 data. We don't want to block here, because that would
360 just cause dropouts anyway, so we always return whatever
361 data is available. If we haven't received enough, either
362 because we've finished playing or we have a buffer
363 underrun, we play silence to fill the unused space. */
364
365 int written_size = GetAudioData(aubuf, size, false);
366 if (written_size && (size > written_size))
367 {
368 // play silence on buffer underrun
369 memset(aubuf + written_size, 0, size - written_size);
370 }
371
372 //Audio received is in SMPTE channel order, reorder to CA unless passthru
373 if (!m_passthru && m_channels == 8)
374 {
376 }
377
378 /* update audiotime (m_bufferedBytes is read by GetBufferedOnSoundcard) */
379 UInt64 nanos = AudioConvertHostTimeToNanos(timestamp -
380 AudioGetCurrentHostTime());
381 m_bufferedBytes = (int)((nanos / 1000000000.0) * // secs
382 (m_effDsp / 100.0) * // frames/sec
383 m_outputBytesPerFrame); // bytes/frame
384
385 return (written_size > 0);
386}
387
388// unneeded and unused in CA
389void AudioOutputCA::WriteAudio([[maybe_unused]] unsigned char *aubuf,
390 [[maybe_unused]] int size)
391{
392}
393
395{
396 return m_bufferedBytes;
397}
398
402std::chrono::milliseconds AudioOutputCA::GetAudiotime(void)
403{
404 std::chrono::milliseconds audbuf_timecode = GetBaseAudBufTimeCode();
405
406 if (audbuf_timecode == 0ms)
407 return 0ms;
408
409 int totalbuffer = audioready() + GetBufferedOnSoundcard();
410
411 return audbuf_timecode - millisecondsFromFloat(totalbuffer * 100000.0 /
414}
415
416/* This callback provides converted audio data to the default output device. */
417OSStatus RenderCallbackAnalog(void *inRefCon,
418 AudioUnitRenderActionFlags *ioActionFlags,
419 const AudioTimeStamp *inTimeStamp,
420 [[maybe_unused]] UInt32 inBusNumber,
421 [[maybe_unused]] UInt32 inNumberFrames,
422 AudioBufferList *ioData)
423{
424 AudioOutputCA *inst = (static_cast<CoreAudioData *>(inRefCon))->mCA;
425
426 if (!inst->RenderAudio((unsigned char *)(ioData->mBuffers[0].mData),
427 ioData->mBuffers[0].mDataByteSize,
428 inTimeStamp->mHostTime))
429 {
430 // play silence if RenderAudio returns false
431 memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
432 *ioActionFlags = kAudioUnitRenderAction_OutputIsSilence;
433 }
434 return noErr;
435}
436
437int AudioOutputCA::GetVolumeChannel([[maybe_unused]] int channel) const
438{
439 // FIXME: this only returns global volume
440 Float32 volume;
441
442 if (!AudioUnitGetParameter(d->mOutputUnit,
443 kHALOutputParam_Volume,
444 kAudioUnitScope_Global, 0, &volume))
445 return (int)lroundf(volume * 100.0F);
446
447 return 0; // error case
448}
449
450void AudioOutputCA::SetVolumeChannel([[maybe_unused]] int channel, int volume)
451{
452 // FIXME: this only sets global volume
453 AudioUnitSetParameter(d->mOutputUnit, kHALOutputParam_Volume,
454 kAudioUnitScope_Global, 0, (volume * 0.01F), 0);
455}
456
457// IOProc style callback for SPDIF audio output
458static OSStatus RenderCallbackSPDIF([[maybe_unused]] AudioDeviceID inDevice,
459 [[maybe_unused]] const AudioTimeStamp *inNow,
460 [[maybe_unused]] const void *inInputData,
461 [[maybe_unused]] const AudioTimeStamp *inInputTime,
462 AudioBufferList *outOutputData,
463 const AudioTimeStamp *inOutputTime,
464 void *inRefCon)
465{
466 auto *d = static_cast<CoreAudioData *>(inRefCon);
467 AudioOutputCA *a = d->mCA;
468 int index = d->mStreamIndex;
469
470 /*
471 * HACK: No idea why this would be the case, but after the second run, we get
472 * incorrect value
473 */
474 if (d->mBytesPerPacket > 0 &&
475 outOutputData->mBuffers[index].mDataByteSize > d->mBytesPerPacket)
476 {
477 outOutputData->mBuffers[index].mDataByteSize = d->mBytesPerPacket;
478 }
479 if (!a->RenderAudio((unsigned char *)(outOutputData->mBuffers[index].mData),
480 outOutputData->mBuffers[index].mDataByteSize,
481 inOutputTime->mHostTime))
482 {
483 // play silence if RenderAudio returns false
484 memset(outOutputData->mBuffers[index].mData, 0,
485 outOutputData->mBuffers[index].mDataByteSize);
486 }
487 return noErr;
488}
489
491{
492 // Reset all the devices to a default 'non-hog' and mixable format.
493 // If we don't do this we may be unable to find the Default Output device.
494 // (e.g. if we crashed last time leaving it stuck in AC-3 mode)
496
498}
499
500CoreAudioData::CoreAudioData(AudioOutputCA *parent, AudioDeviceID deviceID) :
501 mCA(parent)
502{
504 mDeviceID = deviceID;
505}
506
507CoreAudioData::CoreAudioData(AudioOutputCA *parent, QString deviceName) :
508 mCA(parent)
509{
511 mDeviceID = GetDeviceWithName(deviceName);
512 if (!mDeviceID)
513 {
514 // Didn't find specified device, use default one
516 if (deviceName != "Default Output Device")
517 {
518 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:CoreAudioData: \"%1\" not found, using default device %2.")
519 .arg(deviceName).arg(mDeviceID));
520 }
521 }
522 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CoreAudioData: device number is %1")
523 .arg(mDeviceID));
524}
525
526AudioDeviceID CoreAudioData::GetDeviceWithName(const QString &deviceName)
527{
528 UInt32 size = 0;
529 AudioDeviceID deviceID = 0;
530 AudioObjectPropertyAddress pa
531 {
532 kAudioHardwarePropertyDevices,
533 kAudioObjectPropertyScopeGlobal,
535 };
536
537 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
538 0, nullptr, &size);
539 if (err)
540 {
541 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyDataSize: Unable to retrieve the property sizes. "
542 "Error [%1]")
543 .arg(err));
544 return deviceID;
545 }
546
547 UInt32 deviceCount = size / sizeof(AudioDeviceID);
548 std::vector<AudioDeviceID> devices = {};
549 devices.resize(deviceCount);
550
551 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
552 0, nullptr, &size, devices.data());
553 if (err)
554 {
555 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetDeviceWithName: Unable to retrieve the list of available devices. "
556 "Error [%1]")
557 .arg(err));
558 }
559 else
560 {
561 for (const auto & dev : devices)
562 {
563 CoreAudioData device(nullptr, dev);
564 if (device.GetTotalOutputChannels() == 0)
565 continue;
566 QString *name = device.GetName();
567 if (name && *name == deviceName)
568 {
569 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetDeviceWithName: Found: %1").arg(*name));
570 deviceID = dev;
571 delete name;
572 }
573 if (deviceID)
574 break;
575 }
576 }
577 return deviceID;
578}
579
581{
582 UInt32 paramSize;
583 AudioDeviceID deviceId = 0;
584 AudioObjectPropertyAddress pa
585 {
586 kAudioHardwarePropertyDefaultOutputDevice,
587 kAudioObjectPropertyScopeGlobal,
589 };
590
591 // Find the ID of the default Device
592 paramSize = sizeof(deviceId);
593 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
594 0, nullptr, &paramSize, &deviceId);
595 if (err == noErr)
596 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetDefaultOutputDevice: default device ID = %1").arg(deviceId));
597 else
598 {
599 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetDefaultOutputDevice: could not get default audio device: [%1]")
600 .arg(OSS_STATUS(err)));
601 deviceId = 0;
602 }
603 return deviceId;
604}
605
607{
608 if (!mDeviceID)
609 return 0;
610 UInt32 channels = 0;
611 UInt32 size = 0;
612 AudioObjectPropertyAddress pa
613 {
614 kAudioDevicePropertyStreamConfiguration,
615 kAudioDevicePropertyScopeOutput, // Scope needs to be set to output to find output streams
617 };
618
619 OSStatus err = AudioObjectGetPropertyDataSize(mDeviceID, &pa,
620 0, nullptr, &size);
621 if (err)
622 {
623 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetTotalOutputChannels: Unable to get "
624 "size of device output channels - id: %1 Error = [%2]")
625 .arg(mDeviceID)
626 .arg(err));
627 return 0;
628 }
629
630 auto *pList = (AudioBufferList *)malloc(size);
631 err = AudioObjectGetPropertyData(mDeviceID, &pa,
632 0, nullptr, &size, pList);
633 if (!err)
634 {
635 for (UInt32 buffer = 0; buffer < pList->mNumberBuffers; buffer++)
636 channels += pList->mBuffers[buffer].mNumberChannels;
637 }
638 else
639 {
640 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetTotalOutputChannels: Unable to get "
641 "total device output channels - id: %1 Error = [%2]")
642 .arg(mDeviceID)
643 .arg(err));
644 }
645 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetTotalOutputChannels: Found %1 channels in %2 buffers")
646 .arg(channels).arg(pList->mNumberBuffers));
647 free(pList);
648 return channels;
649}
650
652{
653 if (!mDeviceID)
654 return nullptr;
655
656 AudioObjectPropertyAddress pa
657 {
658 kAudioObjectPropertyName,
659 kAudioObjectPropertyScopeGlobal,
661 };
662
663 CFStringRef name;
664 UInt32 propertySize = sizeof(CFStringRef);
665 OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa,
666 0, nullptr, &propertySize, &name);
667 if (err)
668 {
669 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:AudioObjectGetPropertyData for kAudioObjectPropertyName error: [%1]")
670 .arg(err));
671 return nullptr;
672 }
673 char *cname = new char[CFStringGetLength(name) + 1];
674 CFStringGetCString(name, cname, CFStringGetLength(name) + 1, kCFStringEncodingUTF8);
675 auto *qname = new QString(cname);
676 delete[] cname;
677 return qname;
678}
679
681{
682 UInt32 val = 0;
683 UInt32 size = sizeof(val);
684 AudioObjectPropertyAddress pa
685 {
686 kAudioHardwarePropertyHogModeIsAllowed,
687 kAudioObjectPropertyScopeGlobal,
689 };
690
691 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr, &size, &val);
692 if (err)
693 {
694 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetAutoHogMode: Unable to get auto 'hog' mode. Error = [%1]")
695 .arg(err));
696 return false;
697 }
698 return (val == 1);
699}
700
702{
703 UInt32 val = enable ? 1 : 0;
704 AudioObjectPropertyAddress pa
705 {
706 kAudioHardwarePropertyHogModeIsAllowed,
707 kAudioObjectPropertyScopeGlobal,
709 };
710
711 OSStatus err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
712 sizeof(val), &val);
713 if (err)
714 {
715 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetAutoHogMode: Unable to set auto 'hog' mode. Error = [%1]")
716 .arg(err));
717 }
718}
719
721{
722 pid_t PID;
723 UInt32 PIDsize = sizeof(PID);
724 AudioObjectPropertyAddress pa
725 {
726 kAudioDevicePropertyHogMode,
727 kAudioObjectPropertyScopeGlobal,
729 };
730
731 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
732 &PIDsize, &PID);
733 if (err != noErr)
734 {
735 // This is not a fatal error.
736 // Some drivers simply don't support this property
737 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetHogStatus: unable to check: [%1]")
738 .arg(err));
739 return -1;
740 }
741 return PID;
742}
743
745{
746 AudioObjectPropertyAddress pa
747 {
748 kAudioDevicePropertyHogMode,
749 kAudioObjectPropertyScopeGlobal,
751 };
752
753 // According to Jeff Moore (Core Audio, Apple), Setting kAudioDevicePropertyHogMode
754 // is a toggle and the only way to tell if you do get hog mode is to compare
755 // the returned pid against getpid, if the match, you have hog mode, if not you don't.
756 if (!mDeviceID)
757 return false;
758
759 if (hog)
760 {
761 if (mHog == -1) // Not already set
762 {
763 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Setting 'hog' status on device %1")
764 .arg(mDeviceID));
765 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
766 sizeof(mHog), &mHog);
767 if (err || mHog != getpid())
768 {
769 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetHogStatus: Unable to set 'hog' status. Error = [%1]")
770 .arg(OSS_STATUS(err)));
771 return false;
772 }
773 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Successfully set 'hog' status on device %1")
774 .arg(mDeviceID));
775 }
776 }
777 else
778 {
779 if (mHog > -1) // Currently Set
780 {
781 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Releasing 'hog' status on device %1")
782 .arg(mDeviceID));
783 pid_t hogPid = -1;
784 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
785 sizeof(hogPid), &hogPid);
786 if (err || hogPid == getpid())
787 {
788 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetHogStatus: Unable to release 'hog' status. Error = [%1]")
789 .arg(OSS_STATUS(err)));
790 return false;
791 }
792 mHog = hogPid; // Reset internal state
793 }
794 }
795 return true;
796}
797
799{
800 if (!mDeviceID)
801 return false;
802 int restore = -1;
803 if (mMixerRestore == -1) // This is our first change to this setting. Store the original setting for restore
804 restore = (GetMixingSupport() ? 1 : 0);
805 UInt32 mixEnable = mix ? 1 : 0;
806 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetMixingSupport: %1abling mixing for device %2")
807 .arg(mix ? "En" : "Dis")
808 .arg(mDeviceID));
809
810 AudioObjectPropertyAddress pa
811 {
812 kAudioDevicePropertySupportsMixing,
813 kAudioObjectPropertyScopeGlobal,
815 };
816 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
817 sizeof(mixEnable), &mixEnable);
818 if (err)
819 {
820 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetMixingSupport: Unable to set MixingSupport to %1. Error = [%2]")
821 .arg(mix ? "'On'" : "'Off'")
822 .arg(OSS_STATUS(err)));
823 return false;
824 }
825 if (mMixerRestore == -1)
826 mMixerRestore = restore;
827 return true;
828}
829
831{
832 if (!mDeviceID)
833 return false;
834 UInt32 val = 0;
835 UInt32 size = sizeof(val);
836 AudioObjectPropertyAddress pa
837 {
838 kAudioDevicePropertySupportsMixing,
839 kAudioObjectPropertyScopeGlobal,
841 };
842 OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa, 0, nullptr,
843 &size, &val);
844 if (err)
845 return false;
846 return (val > 0);
847}
848
853{
854 OSStatus err;
855 UInt32 listSize;
856 AudioStreamIDVec vec {};
857
858 AudioObjectPropertyAddress pa
859 {
860 kAudioDevicePropertyStreams,
861 kAudioObjectPropertyScopeGlobal,
863 };
864
865 err = AudioObjectGetPropertyDataSize(d, &pa,
866 0, nullptr, &listSize);
867 if (err != noErr)
868 {
869 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:StreamsList: could not get list size: [%1]")
870 .arg(OSS_STATUS(err)));
871 return {};
872 }
873 try
874 {
875 // Bugfix: vec.reserve will not change size of vector since contents are updated directly via memory copy
876 // In general all std::vector arrays have been changed from reserve to resize.
877 vec.resize(listSize / sizeof(AudioStreamID));
878 }
879 catch (...)
880 {
881 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:StreamsList(): out of memory?");
882 return {};
883 }
884
885 err = AudioObjectGetPropertyData(d, &pa,
886 0, nullptr, &listSize, vec.data());
887 if (err != noErr)
888 {
889 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:StreamsList: could not get list: [%1]")
890 .arg(OSS_STATUS(err)));
891 return {};
892 }
893
894 return vec;
895}
896
898{
899 OSStatus err;
901 UInt32 listSize;
902
903 AudioObjectPropertyAddress pa
904 {
905 // Bugfix: kAudioStreamPropertyPhysicalFormats, is meant to only give array of AudioStreamBasicDescription
906 kAudioStreamPropertyAvailablePhysicalFormats, // gives array of AudioStreamRangedDescription
907 kAudioObjectPropertyScopeGlobal,
909 };
910
911 // Retrieve all the stream formats supported by this output stream
912 err = AudioObjectGetPropertyDataSize(s, &pa, 0, nullptr, &listSize);
913 if (err != noErr)
914 {
915 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:FormatsList(): couldn't get list size: [%1]")
916 .arg(OSS_STATUS(err)));
917 return {};
918 }
919
920 try
921 {
922 vec.resize(listSize / sizeof(AudioStreamRangedDescription));
923 }
924 catch (...)
925 {
926 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:FormatsList(): out of memory?");
927 return {};
928 }
929
930 err = AudioObjectGetPropertyData(s, &pa, 0, nullptr, &listSize, vec.data());
931 if (err != noErr)
932 {
933 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:FormatsList: couldn't get list: [%1]")
934 .arg(OSS_STATUS(err)));
935 return {};
936 }
937
938 return vec;
939}
940
941static UInt32 sNumberCommonSampleRates = 15;
942static Float64 sCommonSampleRates[] = {
943 8000.0, 11025.0, 12000.0,
944 16000.0, 22050.0, 24000.0,
945 32000.0, 44100.0, 48000.0,
946 64000.0, 88200.0, 96000.0,
947 128000.0, 176400.0, 192000.0 };
948
949static bool IsRateCommon(Float64 inRate)
950{
951 bool theAnswer = false;
952 for(UInt32 i = 0; !theAnswer && (i < sNumberCommonSampleRates); i++)
953 {
954 theAnswer = inRate == sCommonSampleRates[i];
955 }
956 return theAnswer;
957}
958
960{
961 OSStatus err;
962 AudioValueRangeVec ranges;
963 RatesVec finalvec;
964 UInt32 listSize;
965
966 AudioObjectPropertyAddress pa
967 {
968 kAudioDevicePropertyAvailableNominalSampleRates,
969 kAudioObjectPropertyScopeGlobal,
971 };
972
973 // retrieve size of rate list
974 err = AudioObjectGetPropertyDataSize(d, &pa, 0, nullptr, &listSize);
975 if (err != noErr)
976 {
977 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:RatesList(): couldn't get data rate list size: [%1]")
978 .arg(err));
979 return {};
980 }
981
982 try
983 {
984 ranges.resize(listSize / sizeof(AudioValueRange));
985 finalvec.reserve(listSize / sizeof(AudioValueRange));
986 }
987 catch (...)
988 {
989 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:RatesList(): out of memory?");
990 return {};
991 }
992
993 err = AudioObjectGetPropertyData(d, &pa, 0, nullptr, &listSize, ranges.data());
994 if (err != noErr)
995 {
996 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:RatesList(): couldn't get list: [%1]")
997 .arg(err));
998 return {};
999 }
1000
1001 // iterate through the ranges and add the minimum, maximum, and common rates in between
1002 UInt32 theFirstIndex;
1003 UInt32 theLastIndex = 0;
1004 for(auto range : ranges)
1005 {
1006 theFirstIndex = theLastIndex;
1007 // find the index of the first common rate greater than or equal to the minimum
1008 while((theFirstIndex < sNumberCommonSampleRates) && (sCommonSampleRates[theFirstIndex] < range.mMinimum))
1009 theFirstIndex++;
1010
1011 if (theFirstIndex >= sNumberCommonSampleRates)
1012 break;
1013
1014 theLastIndex = theFirstIndex;
1015 // find the index of the first common rate greater than or equal to the maximum
1016 while((theLastIndex < sNumberCommonSampleRates) && (sCommonSampleRates[theLastIndex] < range.mMaximum))
1017 {
1018 finalvec.push_back(sCommonSampleRates[theLastIndex]);
1019 theLastIndex++;
1020 }
1021 if (IsRateCommon(range.mMinimum))
1022 finalvec.push_back(range.mMinimum);
1023 else if (IsRateCommon(range.mMaximum))
1024 finalvec.push_back(range.mMaximum);
1025 }
1026
1027 return finalvec;
1028}
1029
1030bool CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru, ChannelsArr& chans)
1031{
1032 AudioStreamIDVec streams;
1034 bool founddigital = false;
1035
1036 chans.fill(false);
1037
1038 streams = StreamsList(mDeviceID);
1039 if (streams.empty())
1040 return false;
1041
1042 if (passthru)
1043 {
1044 for (auto stream : streams)
1045 {
1046 formats = FormatsList(stream);
1047 if (formats.empty())
1048 continue;
1049
1050 // Find a stream with a cac3 stream
1051 for (auto format : formats)
1052 {
1053 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ChannelsList: (passthru) found format: %1")
1054 .arg(StreamDescriptionToString(format.mFormat)));
1055 // Add supported number of channels
1056 if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
1057 chans[format.mFormat.mChannelsPerFrame-1] = true;
1058
1059 if (format.mFormat.mFormatID == 'IAC3' ||
1060 format.mFormat.mFormatID == kAudioFormat60958AC3)
1061 {
1062 // By default AC3 has 6 (5.1) channels but CoreAudio seems to set mChannelsPerFrame to 2
1063 // and considers it a "2 channel Encoded Digital Audio"
1064 chans[6-1] = true;
1065 // chans[format.mFormat.mChannelsPerFrame-1] = true;
1066 founddigital = true;
1067 }
1068 }
1069 }
1070 }
1071
1072 if (!founddigital)
1073 {
1074 for (auto stream : streams)
1075 {
1076 formats = FormatsList(stream);
1077 if (formats.empty())
1078 continue;
1079 for (auto format : formats)
1080 {
1081 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ChannelsList: (!founddigital) found format: %1")
1082 .arg(StreamDescriptionToString(format.mFormat)));
1083 if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
1084 chans[format.mFormat.mChannelsPerFrame-1] = true;
1085 }
1086 }
1087 }
1088 return true;
1089}
1090
1092{
1093 AudioComponentDescription desc;
1094 AudioStreamBasicDescription DeviceFormat;
1095 AudioChannelLayout *layout;
1096 AudioChannelLayout new_layout;
1097 AudioDeviceID defaultDevice = GetDefaultOutputDevice();
1098 AudioObjectPropertyAddress pa
1099 {
1100 kAudioHardwarePropertyDevices,
1101 kAudioObjectPropertyScopeGlobal,
1103 };
1104
1105 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenAnalog: Entering");
1106
1107 desc.componentType = kAudioUnitType_Output;
1108 if (defaultDevice == mDeviceID)
1109 {
1110 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
1111 }
1112 else
1113 {
1114 desc.componentSubType = kAudioUnitSubType_HALOutput;
1115 }
1116 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
1117 desc.componentFlags = 0;
1118 desc.componentFlagsMask = 0;
1119 mDigitalInUse = false;
1120
1121 AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
1122 if (comp == nullptr)
1123 {
1124 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:OpenAnalog: AudioComponentFindNext failed");
1125 return false;
1126 }
1127
1128 OSErr err = AudioComponentInstanceNew(comp, &mOutputUnit);
1129 if (err)
1130 {
1131 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioComponentInstanceNew returned %1")
1132 .arg(err));
1133 return false;
1134 }
1135
1136 // Check if we have IO
1137 UInt32 hasIO = 0;
1138 UInt32 size_hasIO = sizeof(hasIO);
1139 err = AudioUnitGetProperty(mOutputUnit,
1140 kAudioOutputUnitProperty_HasIO,
1141 kAudioUnitScope_Output,
1142 0,
1143 &hasIO, &size_hasIO);
1144 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: HasIO (output) = %1").arg(hasIO));
1145 if (!hasIO)
1146 {
1147 UInt32 enableIO = 1;
1148 err = AudioUnitSetProperty(mOutputUnit,
1149 kAudioOutputUnitProperty_EnableIO,
1150 kAudioUnitScope_Global,
1151 0,
1152 &enableIO, sizeof(enableIO));
1153 if (err)
1154 {
1155 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: failed enabling IO: %1")
1156 .arg(err));
1157 }
1158 hasIO = 0;
1159 err = AudioUnitGetProperty(mOutputUnit,
1160 kAudioOutputUnitProperty_HasIO,
1161 kAudioUnitScope_Output,
1162 0,
1163 &hasIO, &size_hasIO);
1164 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::HasIO = %1").arg(hasIO));
1165 }
1166
1167 /*
1168 * We shouldn't have to do this distinction, however for some unknown reasons
1169 * assigning device to AudioUnit fail when switching from SPDIF mode
1170 */
1171 if (defaultDevice != mDeviceID)
1172 {
1173 err = AudioUnitSetProperty(mOutputUnit,
1174 kAudioOutputUnitProperty_CurrentDevice,
1175 kAudioUnitScope_Global,
1176 0,
1177 &mDeviceID, sizeof(mDeviceID));
1178 if (err)
1179 {
1180 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: Unable to set current device to %1. Error = %2")
1181 .arg(mDeviceID)
1182 .arg(err));
1183 return -1;
1184 }
1185 }
1186 /* Get the current format */
1187 UInt32 param_size = sizeof(AudioStreamBasicDescription);
1188
1189 err = AudioUnitGetProperty(mOutputUnit,
1190 kAudioUnitProperty_StreamFormat,
1191 kAudioUnitScope_Input,
1192 0,
1193 &DeviceFormat,
1194 &param_size );
1195 if (err)
1196 {
1197 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: Unable to retrieve current stream format: [%1]")
1198 .arg(err));
1199 }
1200 else
1201 {
1202 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: current format is: %1")
1203 .arg(StreamDescriptionToString(DeviceFormat)));
1204 }
1205 /* Get the channel layout of the device side of the unit */
1206 pa.mSelector = kAudioDevicePropertyPreferredChannelLayout;
1207 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1208 0, nullptr, &param_size);
1209
1210 if(!err)
1211 {
1212 layout = (AudioChannelLayout *) malloc(param_size);
1213
1214 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1215 0, nullptr, &param_size, layout);
1216
1217 /* We need to "fill out" the ChannelLayout, because there are multiple ways that it can be set */
1218 if(layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
1219 {
1220 /* bitmap defined channellayout */
1221 err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
1222 sizeof(UInt32), &layout->mChannelBitmap,
1223 &param_size,
1224 layout);
1225 if (err)
1226 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenAnalog: Can't retrieve current channel layout");
1227 }
1228 else if(layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions )
1229 {
1230 /* layouttags defined channellayout */
1231 err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
1232 sizeof(AudioChannelLayoutTag),
1233 &layout->mChannelLayoutTag,
1234 &param_size,
1235 layout);
1236 if (err)
1237 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenAnalog: Can't retrieve current channel layout");
1238 }
1239
1240 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: Layout of AUHAL has %1 channels")
1241 .arg(layout->mNumberChannelDescriptions));
1242
1243 int channels_found = 0;
1244 for(UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++)
1245 {
1246 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: this is channel: %1")
1247 .arg(layout->mChannelDescriptions[i].mChannelLabel));
1248
1249 switch( layout->mChannelDescriptions[i].mChannelLabel)
1250 {
1251 case kAudioChannelLabel_Left:
1252 case kAudioChannelLabel_Right:
1253 case kAudioChannelLabel_Center:
1254 case kAudioChannelLabel_LFEScreen:
1255 case kAudioChannelLabel_LeftSurround:
1256 case kAudioChannelLabel_RightSurround:
1257 case kAudioChannelLabel_RearSurroundLeft:
1258 case kAudioChannelLabel_RearSurroundRight:
1259 case kAudioChannelLabel_CenterSurround:
1260 channels_found++;
1261 break;
1262 default:
1263 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::unrecognized channel form provided by driver: %1")
1264 .arg(layout->mChannelDescriptions[i].mChannelLabel));
1265 }
1266 }
1267 if(channels_found == 0)
1268 {
1269 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:Audio device is not configured. "
1270 "You should configure your speaker layout with "
1271 "the \"Audio Midi Setup\" utility in /Applications/"
1272 "Utilities.");
1273 }
1274 free(layout);
1275 }
1276 else
1277 {
1278 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:this driver does not support kAudioDevicePropertyPreferredChannelLayout.");
1279 }
1280
1281 memset (&new_layout, 0, sizeof(new_layout));
1282 switch(mCA->m_channels)
1283 {
1284 case 1:
1285 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
1286 break;
1287 case 2:
1288 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
1289 break;
1290 case 6:
1291 // 3F2-LFE L R C LFE LS RS
1292 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_5_1;
1293 break;
1294 case 8:
1295 // We need
1296 // 3F4-LFE L R C LFE Rls Rrs LS RS
1297 // but doesn't exist, so we'll swap channels later
1298 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; // L R C LFE Ls Rs Lc Rc
1299 break;
1300 }
1301 /* Set new_layout as the layout */
1302 err = AudioUnitSetProperty(mOutputUnit,
1303 kAudioUnitProperty_AudioChannelLayout,
1304 kAudioUnitScope_Input,
1305 0,
1306 &new_layout, sizeof(new_layout));
1307 if (err)
1308 {
1309 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: couldn't set channels layout [%1]")
1310 .arg(err));
1311 }
1312
1313 if(new_layout.mNumberChannelDescriptions > 0)
1314 free(new_layout.mChannelDescriptions);
1315
1316 // Set up the audio output unit
1317 int formatFlags;
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;
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;
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;
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;
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;
1706 UInt32 paramSize;
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 sleep(1); // 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
static Float64 sCommonSampleRates[]
#define CHANNELS_MAX
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)
#define CHANNELS_MIN
static bool IsRateCommon(Float64 inRate)
char * UInt32ToFourCC(const UInt32 *pVal)
std::vector< int > RatesVec
static void _ReorderSmpteToCA(AudioDataType *buf, uint frames)
static constexpr int8_t kMythAudioObjectPropertyElementMain
std::array< bool, CHANNELS_MAX > ChannelsArr
static void ReorderSmpteToCA(void *buf, uint frames, AudioFormat format)
static UInt32 sNumberCommonSampleRates
@ FORMAT_FLT
@ FORMAT_S16
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)
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
bool GetMixingSupport()
static pid_t GetHogStatus()
static RatesVec RatesList(AudioDeviceID d)
static AudioStreamIDVec StreamsList(AudioDeviceID d)
Get a list of all the streams on this device.
AudioDeviceID mDeviceID
bool ChannelsList(AudioDeviceID d, bool passthru, ChannelsArr &chans)
UInt32 mBytesPerPacket
static int AudioStreamChangeFormat(AudioStreamID s, AudioStreamBasicDescription format)
AudioUnit mOutputUnit
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
QString * GetName()
int GetTotalOutputChannels()
static void ResetAudioDevices()
Reset any devices with an AC3 stream back to a Linear PCM so that they can become a default output de...
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
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:123
unsigned int uint
Definition: freesurround.h:24
static const iso6937table * d
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::milliseconds > millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:91
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189