2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "cores/AudioEngine/Sinks/AESinkDARWINIOS.h"
22 #include "cores/AudioEngine/Utils/AEUtil.h"
23 #include "cores/AudioEngine/Utils/AERingBuffer.h"
24 #include "cores/AudioEngine/Sinks/osx/CoreAudioHelpers.h"
25 #include "osx/DarwinUtils.h"
26 #include "utils/log.h"
27 #include "utils/StringUtils.h"
28 #include "threads/Condition.h"
29 #include "windowing/WindowingFactory.h"
32 #include <AudioToolbox/AudioToolbox.h>
34 #define CA_MAX_CHANNELS 8
35 static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
36 AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
40 /***************************************************************************************/
41 /***************************************************************************************/
42 #if DO_440HZ_TONE_TEST
43 static void SineWaveGeneratorInitWithFrequency(SineWaveGenerator *ctx, double frequency, double samplerate)
46 // frequency in cycles per second
47 // 2*PI radians per sine wave cycle
48 // sample rate in samples per second
51 // cycles radians seconds radians
52 // ------ * ------- * ------- = -------
53 // second cycle sample sample
54 ctx->currentPhase = 0.0;
55 ctx->phaseIncrement = frequency * 2*M_PI / samplerate;
58 static int16_t SineWaveGeneratorNextSampleInt16(SineWaveGenerator *ctx)
60 int16_t sample = INT16_MAX * sinf(ctx->currentPhase);
62 ctx->currentPhase += ctx->phaseIncrement;
63 // Keep the value between 0 and 2*M_PI
64 while (ctx->currentPhase > 2*M_PI)
65 ctx->currentPhase -= 2*M_PI;
69 static float SineWaveGeneratorNextSampleFloat(SineWaveGenerator *ctx)
71 float sample = MAXFLOAT * sinf(ctx->currentPhase);
73 ctx->currentPhase += ctx->phaseIncrement;
74 // Keep the value between 0 and 2*M_PI
75 while (ctx->currentPhase > 2*M_PI)
76 ctx->currentPhase -= 2*M_PI;
82 /***************************************************************************************/
83 /***************************************************************************************/
90 bool open(AudioStreamBasicDescription outputFormat);
98 unsigned int write(uint8_t *data, unsigned int byte_count);
99 unsigned int chunkSize() { return m_bufferDuration * m_sampleRate; }
100 unsigned int getRealisedSampleRate() { return m_outputFormat.mSampleRate; }
101 static Float64 getCoreAudioRealisedSampleRate();
104 void setCoreAudioBuffersize();
105 bool setCoreAudioInputFormat();
106 void setCoreAudioPreferredSampleRate();
108 bool checkAudioRoute();
109 bool checkSessionProperties();
110 bool activateAudioSession();
111 void deactivateAudioSession();
114 static void sessionPropertyCallback(void *inClientData,
115 AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData);
117 static void sessionInterruptionCallback(void *inClientData, UInt32 inInterruption);
119 static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
120 const AudioTimeStamp *inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames,
121 AudioBufferList *ioData);
126 AudioUnit m_audioUnit;
127 AudioStreamBasicDescription m_outputFormat;
128 AERingBuffer *m_buffer;
131 Float32 m_outputVolume;
132 Float32 m_outputLatency;
133 Float32 m_bufferDuration;
135 unsigned int m_sampleRate;
136 unsigned int m_frameSize;
137 unsigned int m_frames;
140 bool m_playing_saved;
141 volatile bool m_started;
144 CAAudioUnitSink::CAAudioUnitSink()
145 : m_initialized(false)
149 , m_playing_saved(false)
154 CAAudioUnitSink::~CAAudioUnitSink()
159 bool CAAudioUnitSink::open(AudioStreamBasicDescription outputFormat)
163 m_outputFormat = outputFormat;
164 m_outputLatency = 0.0;
165 m_bufferDuration= 0.0;
166 m_outputVolume = 1.0;
167 m_sampleRate = (unsigned int)outputFormat.mSampleRate;
168 m_frameSize = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8;
170 /* TODO: Reduce the size of this buffer, pre-calculate the size based on how large
171 the buffers are that CA calls us with in the renderCallback - perhaps call
172 the checkSessionProperties() before running this? */
173 m_buffer = new AERingBuffer(16384);
178 bool CAAudioUnitSink::close()
180 deactivateAudioSession();
189 bool CAAudioUnitSink::play(bool mute)
193 if (activateAudioSession())
195 CAAudioUnitSink::mute(mute);
196 m_playing = !AudioOutputUnitStart(m_audioUnit);
203 bool CAAudioUnitSink::mute(bool mute)
210 bool CAAudioUnitSink::pause()
213 m_playing = AudioOutputUnitStop(m_audioUnit);
218 double CAAudioUnitSink::getDelay()
220 double delay = (double)m_buffer->GetReadSize() / m_frameSize;
221 delay /= m_sampleRate;
222 delay += m_bufferDuration + m_outputLatency;
227 double CAAudioUnitSink::cacheSize()
229 return (double)m_buffer->GetMaxSize() / (double)(m_frameSize * m_sampleRate);
232 CCriticalSection mutex;
233 XbmcThreads::ConditionVariable condVar;
235 unsigned int CAAudioUnitSink::write(uint8_t *data, unsigned int frames)
237 if (m_buffer->GetWriteSize() < frames * m_frameSize)
238 { // no space to write - wait for a bit
239 CSingleLock lock(mutex);
243 condVar.wait(lock, 900 * frames / m_sampleRate);
246 unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / m_frameSize);
248 m_buffer->Write(data, write_frames * m_frameSize);
253 void CAAudioUnitSink::drain()
255 CCriticalSection mutex;
256 unsigned int bytes = m_buffer->GetReadSize();
259 CSingleLock lock(mutex);
260 condVar.wait(mutex, 900 * bytes / (m_sampleRate * m_frameSize));
261 bytes = m_buffer->GetReadSize();
265 void CAAudioUnitSink::setCoreAudioBuffersize()
267 #if !TARGET_IPHONE_SIMULATOR
268 OSStatus status = noErr;
269 // set the buffer size, this affects the number of samples
270 // that get rendered every time the audio callback is fired.
271 Float32 preferredBufferSize = 512 * m_outputFormat.mChannelsPerFrame / m_outputFormat.mSampleRate;
272 CLog::Log(LOGNOTICE, "%s setting buffer duration to %f", __PRETTY_FUNCTION__, preferredBufferSize);
273 status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration,
274 sizeof(preferredBufferSize), &preferredBufferSize);
276 CLog::Log(LOGWARNING, "%s preferredBufferSize couldn't be set (error: %d)", __PRETTY_FUNCTION__, (int)status);
280 bool CAAudioUnitSink::setCoreAudioInputFormat()
282 // Set the output stream format
283 UInt32 ioDataSize = sizeof(AudioStreamBasicDescription);
284 OSStatus status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_StreamFormat,
285 kAudioUnitScope_Input, 0, &m_outputFormat, ioDataSize);
288 CLog::Log(LOGERROR, "%s error setting stream format on audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
294 void CAAudioUnitSink::setCoreAudioPreferredSampleRate()
296 Float64 preferredSampleRate = m_outputFormat.mSampleRate;
297 CLog::Log(LOGNOTICE, "%s requesting hw samplerate %f", __PRETTY_FUNCTION__, preferredSampleRate);
298 OSStatus status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate,
299 sizeof(preferredSampleRate), &preferredSampleRate);
301 CLog::Log(LOGWARNING, "%s preferredSampleRate couldn't be set (error: %d)", __PRETTY_FUNCTION__, (int)status);
304 Float64 CAAudioUnitSink::getCoreAudioRealisedSampleRate()
306 Float64 outputSampleRate = 0.0;
307 UInt32 ioDataSize = sizeof(outputSampleRate);
308 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
309 &ioDataSize, &outputSampleRate) != noErr)
310 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareSampleRate", __FUNCTION__);
311 return outputSampleRate;
314 bool CAAudioUnitSink::setupAudio()
316 OSStatus status = noErr;
317 if (m_setup && m_audioUnit)
320 // Audio Session Setup
321 UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
322 status = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
323 sizeof(sessionCategory), &sessionCategory);
326 CLog::Log(LOGERROR, "%s error setting sessioncategory (error: %d)", __PRETTY_FUNCTION__, (int)status);
330 AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,
331 sessionPropertyCallback, this);
333 AudioSessionAddPropertyListener(kAudioSessionProperty_CurrentHardwareOutputVolume,
334 sessionPropertyCallback, this);
336 if (AudioSessionSetActive(true) != noErr)
340 // Describe a default output unit.
341 AudioComponentDescription description = {};
342 description.componentType = kAudioUnitType_Output;
343 description.componentSubType = kAudioUnitSubType_RemoteIO;
344 description.componentManufacturer = kAudioUnitManufacturer_Apple;
347 AudioComponent component;
348 component = AudioComponentFindNext(NULL, &description);
349 status = AudioComponentInstanceNew(component, &m_audioUnit);
352 CLog::Log(LOGERROR, "%s error creating audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
356 setCoreAudioPreferredSampleRate();
358 // Get the output samplerate for knowing what was setup in reality
359 Float64 realisedSampleRate = getCoreAudioRealisedSampleRate();
360 if (m_outputFormat.mSampleRate != realisedSampleRate)
362 CLog::Log(LOGNOTICE, "%s couldn't set requested samplerate %d, coreaudio will resample to %d instead", __PRETTY_FUNCTION__, (int)m_outputFormat.mSampleRate, (int)realisedSampleRate);
363 // if we don't ca to resample - but instead let activeae resample -
364 // reflect the realised samplerate to the outputformat here
365 // well maybe it is handy in the future - as of writing this
366 // ca was about 6 times faster then activeae ;)
367 //m_outputFormat.mSampleRate = realisedSampleRate;
368 //m_sampleRate = realisedSampleRate;
371 setCoreAudioBuffersize();
372 if (!setCoreAudioInputFormat())
375 // Attach a render callback on the unit
376 AURenderCallbackStruct callbackStruct = {};
377 callbackStruct.inputProc = renderCallback;
378 callbackStruct.inputProcRefCon = this;
379 status = AudioUnitSetProperty(m_audioUnit,
380 kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
381 0, &callbackStruct, sizeof(callbackStruct));
384 CLog::Log(LOGERROR, "%s error setting render callback for audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
388 status = AudioUnitInitialize(m_audioUnit);
391 CLog::Log(LOGERROR, "%s error initializing audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
395 checkSessionProperties();
398 std::string formatString;
399 CLog::Log(LOGNOTICE, "%s setup audio format: %s", __PRETTY_FUNCTION__, StreamDescriptionToString(m_outputFormat, formatString));
404 bool CAAudioUnitSink::checkAudioRoute()
406 // why do we need to know the audio route ?
408 UInt32 propertySize = sizeof(CFStringRef);
409 if (AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route) != noErr)
415 bool CAAudioUnitSink::checkSessionProperties()
420 ioDataSize = sizeof(m_outputVolume);
421 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume,
422 &ioDataSize, &m_outputVolume) != noErr)
423 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareOutputVolume", __FUNCTION__);
425 ioDataSize = sizeof(m_outputLatency);
426 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputLatency,
427 &ioDataSize, &m_outputLatency) != noErr)
428 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareOutputLatency", __FUNCTION__);
430 ioDataSize = sizeof(m_bufferDuration);
431 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration,
432 &ioDataSize, &m_bufferDuration) != noErr)
433 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareIOBufferDuration", __FUNCTION__);
435 CLog::Log(LOGDEBUG, "%s: volume = %f, latency = %f, buffer = %f", __FUNCTION__, m_outputVolume, m_outputLatency, m_bufferDuration);
439 bool CAAudioUnitSink::activateAudioSession()
445 OSStatus osstat = AudioSessionInitialize(NULL, kCFRunLoopDefaultMode, sessionInterruptionCallback, this);
446 if (osstat == kAudioSessionNoError || osstat == kAudioSessionAlreadyInitialized)
447 m_initialized = true;
450 CLog::Log(LOGERROR, "%s error initializing audio session (error: %d)", __PRETTY_FUNCTION__, (int)osstat);
454 if (checkAudioRoute() && setupAudio())
461 void CAAudioUnitSink::deactivateAudioSession()
466 AudioUnitUninitialize(m_audioUnit);
467 AudioComponentInstanceDispose(m_audioUnit), m_audioUnit = NULL;
468 AudioSessionSetActive(false);
469 AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_AudioRouteChange,
470 sessionPropertyCallback, this);
471 AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_CurrentHardwareOutputVolume,
472 sessionPropertyCallback, this);
479 void CAAudioUnitSink::sessionPropertyCallback(void *inClientData,
480 AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData)
482 CAAudioUnitSink *sink = (CAAudioUnitSink*)inClientData;
484 if (inID == kAudioSessionProperty_AudioRouteChange)
486 if (sink->checkAudioRoute())
487 sink->checkSessionProperties();
489 else if (inID == kAudioSessionProperty_CurrentHardwareOutputVolume)
491 if (inData && inDataSize == 4)
492 sink->m_outputVolume = *(float*)inData;
496 void CAAudioUnitSink::sessionInterruptionCallback(void *inClientData, UInt32 inInterruption)
498 CAAudioUnitSink *sink = (CAAudioUnitSink*)inClientData;
500 if (inInterruption == kAudioSessionBeginInterruption)
502 CLog::Log(LOGDEBUG, "Bgn interuption");
503 sink->m_playing_saved = sink->m_playing;
506 else if (inInterruption == kAudioSessionEndInterruption)
508 CLog::Log(LOGDEBUG, "End interuption");
509 if (sink->m_playing_saved)
511 sink->m_playing_saved = false;
512 sink->play(sink->m_mute);
517 OSStatus CAAudioUnitSink::renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
518 const AudioTimeStamp *inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
520 CAAudioUnitSink *sink = (CAAudioUnitSink*)inRefCon;
522 sink->m_started = true;
524 for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
526 // buffers come from CA already zero'd, so just copy what is wanted
527 unsigned int wanted = ioData->mBuffers[i].mDataByteSize;
528 unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
529 sink->m_buffer->Read((unsigned char*)ioData->mBuffers[i].mData, bytes);
531 CLog::Log(LOGERROR, "%s: %sFLOW (%i vs %i) bytes", __FUNCTION__, bytes > wanted ? "OVER" : "UNDER", bytes, wanted);
533 // tell the sink we're good for more data
539 /***************************************************************************************/
540 /***************************************************************************************/
541 static void EnumerateDevices(AEDeviceInfoList &list)
543 CAEDeviceInfo device;
545 device.m_deviceName = "default";
546 device.m_displayName = "Default";
547 device.m_displayNameExtra = "";
548 #if defined(TARGET_DARWIN_IOS_ATV2)
549 device.m_deviceType = AE_DEVTYPE_IEC958;
550 device.m_dataFormats.push_back(AE_FMT_AC3);
551 device.m_dataFormats.push_back(AE_FMT_DTS);
553 // TODO screen changing on ios needs to call
554 // devices changed once this is available in activae
555 if (g_Windowing.GetCurrentScreen() > 0)
557 device.m_deviceType = AE_DEVTYPE_IEC958; //allow passthrough for tvout
558 device.m_dataFormats.push_back(AE_FMT_AC3);
559 device.m_dataFormats.push_back(AE_FMT_DTS);
562 device.m_deviceType = AE_DEVTYPE_PCM;
566 CAEChannelInfo channel_info;
567 for (UInt32 chan = 0; chan < 2; ++chan)
569 if (!device.m_channels.HasChannel(CAChannelMap[chan]))
570 device.m_channels += CAChannelMap[chan];
571 channel_info += CAChannelMap[chan];
574 // there are more supported ( one of those 2 gets resampled
575 // by coreaudio anyway) - but for keeping it save ignore
577 device.m_sampleRates.push_back(44100);
578 device.m_sampleRates.push_back(48000);
580 device.m_dataFormats.push_back(AE_FMT_S16LE);
581 //device.m_dataFormats.push_back(AE_FMT_S24LE3);
582 //device.m_dataFormats.push_back(AE_FMT_S32LE);
583 // AE_FMT_FLOAT is 3% slower on atv2
584 // then S16LE - so leave it out for now
585 //device.m_dataFormats.push_back(AE_FMT_FLOAT);
587 CLog::Log(LOGDEBUG, "EnumerateDevices:Device(%s)" , device.m_deviceName.c_str());
589 list.push_back(device);
592 /***************************************************************************************/
593 /***************************************************************************************/
594 AEDeviceInfoList CAESinkDARWINIOS::m_devices;
596 CAESinkDARWINIOS::CAESinkDARWINIOS()
601 CAESinkDARWINIOS::~CAESinkDARWINIOS()
605 bool CAESinkDARWINIOS::Initialize(AEAudioFormat &format, std::string &device)
608 bool forceRaw = false;
610 std::string devicelower = device;
611 StringUtils::ToLower(devicelower);
612 for (size_t i = 0; i < m_devices.size(); i++)
614 if (devicelower.find(m_devices[i].m_deviceName) != std::string::npos)
616 m_info = m_devices[i];
625 AudioStreamBasicDescription audioFormat = {};
627 // AE_FMT_FLOAT is 3% slower on atv2
628 // then S16LE - so leave it out for now
629 // just leave the code commented in here
630 // as it might come handy at some point maybe ...
631 //if (format.m_dataFormat == AE_FMT_FLOAT)
632 // audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
633 //else// this will be selected when AE wants AC3 or DTS or anything other then float
635 audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
636 if (AE_IS_RAW(format.m_dataFormat))
638 format.m_dataFormat = AE_FMT_S16LE;
641 format.m_channelLayout = m_info.m_channels;
642 format.m_frameSize = format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
645 audioFormat.mFormatID = kAudioFormatLinearPCM;
646 switch(format.m_sampleRate)
653 audioFormat.mSampleRate = 44100;
665 audioFormat.mSampleRate = 48000;
669 if (forceRaw)//make sure input and output samplerate match for preventing resampling
670 audioFormat.mSampleRate = CAAudioUnitSink::getCoreAudioRealisedSampleRate();
672 audioFormat.mFramesPerPacket = 1;
673 audioFormat.mChannelsPerFrame= 2;// ios only supports 2 channels
674 audioFormat.mBitsPerChannel = CAEUtil::DataFormatToBits(format.m_dataFormat);
675 audioFormat.mBytesPerFrame = format.m_frameSize;
676 audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
677 audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
679 #if DO_440HZ_TONE_TEST
680 SineWaveGeneratorInitWithFrequency(&m_SineWaveGenerator, 440.0, audioFormat.mSampleRate);
683 m_audioSink = new CAAudioUnitSink;
684 m_audioSink->open(audioFormat);
686 format.m_frames = m_audioSink->chunkSize();
687 format.m_frameSamples = format.m_frames * audioFormat.mChannelsPerFrame;
688 // reset to the realised samplerate
689 format.m_sampleRate = m_audioSink->getRealisedSampleRate();
692 m_volume_changed = false;
693 m_audioSink->play(false);
698 void CAESinkDARWINIOS::Deinitialize()
704 bool CAESinkDARWINIOS::IsCompatible(const AEAudioFormat &format, const std::string &device)
706 return ((m_format.m_sampleRate == format.m_sampleRate) &&
707 (m_format.m_dataFormat == format.m_dataFormat) &&
708 (m_format.m_channelLayout == format.m_channelLayout));
711 double CAESinkDARWINIOS::GetDelay()
714 return m_audioSink->getDelay();
718 double CAESinkDARWINIOS::GetCacheTotal()
721 return m_audioSink->cacheSize();
725 unsigned int CAESinkDARWINIOS::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking)
728 #if DO_440HZ_TONE_TEST
729 if (m_format.m_dataFormat == AE_FMT_FLOAT)
731 float *samples = (float*)data;
732 for (unsigned int j = 0; j < frames ; j++)
734 float sample = SineWaveGeneratorNextSampleFloat(&m_SineWaveGenerator);
742 int16_t *samples = (int16_t*)data;
743 for (unsigned int j = 0; j < frames ; j++)
745 int16_t sample = SineWaveGeneratorNextSampleInt16(&m_SineWaveGenerator);
752 return m_audioSink->write(data, frames);
756 void CAESinkDARWINIOS::Drain()
759 m_audioSink->drain();
762 bool CAESinkDARWINIOS::HasVolume()
767 void CAESinkDARWINIOS::SetVolume(float scale)
769 // CoreAudio uses fixed steps, reverse scale back to percent
770 float gain = CAEUtil::ScaleToGain(scale);
771 m_volume = CAEUtil::GainToPercent(gain);
772 m_volume_changed = true;
775 void CAESinkDARWINIOS::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
778 EnumerateDevices(m_devices);