3 * Copyright (C) 2010 Team XBMC
6 * This Program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
11 * This Program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with XBMC; see the file COPYING. If not, write to
18 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19 * http://www.gnu.org/copyleft/gpl.html
23 #include "IOSAudioRenderer.h"
24 #include "IOSAudioRingBuffer.h"
25 #include "AudioContext.h"
26 #include "GUISettings.h"
28 #include "utils/log.h"
30 //***********************************************************************************************
31 // Contruction/Destruction
32 //***********************************************************************************************
33 CIOSAudioRenderer::CIOSAudioRenderer() :
37 m_OutputBufferIndex(0),
45 m_Buffer = new IOSAudioRingBuffer();
48 CIOSAudioRenderer::~CIOSAudioRenderer()
54 //***********************************************************************************************
56 //***********************************************************************************************
58 bool CIOSAudioRenderer::Initialize(IAudioCallback* pCallback, const CStdString& device, int iChannels, enum PCMChannels *channelMap, unsigned int uiSamplesPerSec, unsigned int uiBitsPerSample, bool bResample, bool bIsMusic /*Useless Legacy Parameter*/, bool bPassthrough)
60 // Limit to 2.0. It is only used for anloge audio.
61 static enum PCMChannels IOSChannelMap[2] =
62 {PCM_FRONT_LEFT, PCM_FRONT_RIGHT};
64 m_Passthrough = bPassthrough;
66 g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE);
68 bool bAudioOnAllSpeakers(false);
69 g_audioContext.SetupSpeakerConfig(iChannels, bAudioOnAllSpeakers, bIsMusic);
73 g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE_DIGITAL);
75 g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE);
78 m_DataChannels = iChannels;
81 if (!m_Passthrough && channelMap)
83 enum PCMChannels *outLayout;
85 /* set the input format, and get the channel layout so we know what we need to open */
86 outLayout = m_remap.SetInputFormat (iChannels, channelMap, uiBitsPerSample / 8, uiSamplesPerSec);
87 unsigned int outChannels = 0;
88 unsigned int ch = 0, map;
89 while(outLayout[ch] != PCM_INVALID)
91 for(map = 0; map < 8; ++map)
93 if (outLayout[ch] == IOSChannelMap[map])
95 if (map > outChannels)
103 m_remap.SetOutputFormat(++outChannels, IOSChannelMap);
104 if (m_remap.CanRemap())
106 iChannels = outChannels;
107 if (m_DataChannels != (unsigned int)iChannels)
108 CLog::Log(LOGDEBUG, "CIOSAudioRenderer::InitializePCM: Requested channels changed from %i to %i", m_DataChannels, iChannels);
113 m_Channels = iChannels;
115 // Set the input stream format for the AudioUnit
116 // We use the default DefaultOuput AudioUnit, so we only can set the input stream format.
117 // The autput format is automaticaly set to the input format.
118 AudioStreamBasicDescription audioFormat;
119 audioFormat.mFormatID = kAudioFormatLinearPCM; // Data encoding format
121 audioFormat.mFormatFlags = kAudioFormatFlagsCanonical;
122 audioFormat.mChannelsPerFrame = iChannels; // Number of interleaved audiochannels
123 audioFormat.mSampleRate = (Float64)uiSamplesPerSec; // the sample rate of the audio stream
124 audioFormat.mBitsPerChannel = uiBitsPerSample; // Number of bits per sample, per channel
125 audioFormat.mBytesPerFrame = (uiBitsPerSample>>3) * iChannels; // Size of a frame == 1 sample per channel
126 audioFormat.mFramesPerPacket = 1; // The smallest amount of indivisible data. Always 1 for uncompressed audio
127 audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
128 audioFormat.mReserved = 0;
130 // Attach our output object to the device
131 if(!m_AudioDevice.Init(/*m_Passthrough*/ true, &audioFormat, RenderCallback, this))
133 CLog::Log(LOGDEBUG, "CIOSAudioRenderer::Init failed");
137 m_PacketSize = iChannels * (uiBitsPerSample / 8) * 512;
139 m_BufferFrames = m_AudioDevice.FramesPerSlice(m_PacketSize);
142 CLog::Log(LOGDEBUG, "CIOSAudioRenderer::FramesPerSlice bufferFrames == 0\n");
146 m_BytesPerFrame = audioFormat.mBytesPerFrame;
147 m_BitsPerChannel = audioFormat.mBitsPerChannel;
148 m_BytesPerSec = uiSamplesPerSec * (uiBitsPerSample / 8) * iChannels;
149 m_SamplesPerSec = uiSamplesPerSec;
150 m_BufferLen = m_PacketSize * 96;
151 if(m_BufferLen < m_PacketSize || m_BufferLen == 0)
152 m_BufferLen = m_PacketSize;
154 bool success = m_Buffer->Create(m_BufferLen);
155 if(!success || !m_BufferLen)
157 CLog::Log(LOGDEBUG, "CIOSAudioRenderer::Initialize: Error allocation audio buffer size %d.", m_BufferLen);
161 m_EnableVolumeControl = true;
164 if (!m_AudioDevice.SetSessionListener(kAudioSessionProperty_AudioRouteChange, PropertyChangeCallback, this))
168 // Start the audio device
169 if (!m_AudioDevice.Open())
172 // Suspend rendering. We will start once we have some data.
174 m_Initialized = true;
176 CLog::Log(LOGDEBUG, "CIOSAudioRenderer::Initialize: Renderer Configuration - Chunk Len: %u, Max Cache: %u (%0.0fms).", m_PacketSize, m_BufferLen, 1000.0 *(float)m_BufferLen/(float)m_BytesPerSec);
177 CLog::Log(LOGINFO, "CIOSAudioRenderer::Initialize: Successfully configured audio output.");
184 bool CIOSAudioRenderer::Deinitialize()
194 m_AudioDevice.Close();
195 m_Initialized = false;
203 CLog::Log(LOGINFO, "CIOSAudioRenderer::Deinitialize: Renderer has been shut down.");
208 void CIOSAudioRenderer::Flush()
212 // IOSAudioRingBuffer::Reset is not threadsafe but we have
213 // paused here so renderer is not reading from m_Buffer and
214 // we can reset with confidence.
218 //***********************************************************************************************
219 // Transport control methods
220 //***********************************************************************************************
221 bool CIOSAudioRenderer::Pause()
225 m_AudioDevice.Stop();
231 bool CIOSAudioRenderer::Resume()
235 m_AudioDevice.Start();
241 bool CIOSAudioRenderer::Stop()
243 m_AudioDevice.Stop();
251 //***********************************************************************************************
252 // Volume control methods
253 //***********************************************************************************************
254 LONG CIOSAudioRenderer::GetCurrentVolume() const
256 return m_CurrentVolume;
259 void CIOSAudioRenderer::Mute(bool bMute)
263 bool CIOSAudioRenderer::SetCurrentVolume(LONG nVolume)
268 //***********************************************************************************************
269 // Data management methods
270 //***********************************************************************************************
271 unsigned int CIOSAudioRenderer::GetSpace()
273 int free = m_Buffer->GetWriteSize();
274 return (free / m_Channels) * m_DataChannels;
277 unsigned int CIOSAudioRenderer::AddPackets(const void* data, DWORD len)
281 // call channel remapping routine if available and required
282 if (m_remap.CanRemap() && !m_Passthrough)
286 // we might be up or down converting, so convert to number of bytes
287 // that we will get out of remapping the channels and see if that fits.
288 length = (len / m_DataChannels) * m_Channels;
289 if (length > GetSpace())
292 // check buffer fit, we can only accept unit frames, so if less than
293 // a complete frame, we punt.
294 frames = length / m_Channels / (m_BitsPerChannel >> 3);
297 CLog::Log(LOGINFO, "IOSAudioRenderer::AddPackets() - Need complete frame.");
301 uint8_t outData[length];
302 // remap the audio channels using the frame count
303 m_remap.Remap((void*)data, outData, frames);
305 status = m_Buffer->Write(outData, length);
306 // return the number of input bytes we accepted
307 len = (length / m_Channels) * m_DataChannels;
311 // simple case, not remaping or passthough, only have to check
312 // that we have free space in our buffer.
313 status = m_Buffer->Write((unsigned char *)data, len);
317 //only return the length if buffer accepted the data
318 return status == 0 ? len : 0;
321 float CIOSAudioRenderer::GetDelay()
323 return (float)m_Buffer->GetReadSize() / (float)m_BytesPerSec;
326 float CIOSAudioRenderer::GetCacheTime()
328 return (float)(m_BufferLen - GetSpace()) / (float)m_BytesPerSec;
331 float CIOSAudioRenderer::GetCacheTotal()
333 return (float)m_BufferLen / (float)m_BytesPerSec;
336 unsigned int CIOSAudioRenderer::GetChunkLen()
338 return (m_PacketSize / m_Channels) * m_DataChannels;
341 void CIOSAudioRenderer::WaitCompletion()
343 // we don't lock here as we are just checking for zero or non-zero.
345 // The cache is already empty. There is nothing to wait for.
346 if (m_Buffer->GetReadSize() == 0)
351 UInt32 delay = (UInt32)(GetDelay() * 1000.0f) + 10;
354 bool ret = m_RunoutEvent.WaitMSec(delay);
355 if (!ret && m_Buffer->GetReadSize() )
357 //See if there is still some data left in the cache that didn't get played
358 CLog::Log(LOGERROR, "CIOSAudioRenderer::WaitCompletion: Timed-out waiting for runout. Remaining data will be truncated.");
365 //***********************************************************************************************
367 //***********************************************************************************************
368 OSStatus CIOSAudioRenderer::OnRender(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
372 CLog::Log(LOGERROR, "CIOSAudioRenderer::OnRender: Callback to de/unitialized renderer.");
373 ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
379 ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
383 UInt32 bytesRead = m_Buffer->GetReadSize();
384 UInt32 bytesRequested = inNumberFrames * m_BytesPerFrame;
386 if (bytesRead < bytesRequested)
388 m_RunoutEvent.Set(); // Tell anyone who cares that the cache is empty
389 if (m_DoRunout) // We were waiting for a runout. This is not an error.
393 ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
397 m_Buffer->Read((unsigned char *)ioData->mBuffers[m_OutputBufferIndex].mData, bytesRequested);
399 if (!m_EnableVolumeControl && m_CurrentVolume <= VOLUME_MINIMUM)
400 ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
402 ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = bytesRequested;
407 OSStatus CIOSAudioRenderer::RenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
409 return ((CIOSAudioRenderer*)inRefCon)->OnRender(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
412 // Static Callback from AudioUnit
413 void CIOSAudioRenderer::PropertyChanged(AudioSessionPropertyID inID, UInt32 inDataSize, const void* inPropertyValue)
415 CLog::Log(LOGERROR, "CIOSAudioRenderer::PropertyChanged: inID %d.", (int)inID);
418 void CIOSAudioRenderer::PropertyChangeCallback(void* inClientData, AudioSessionPropertyID inID, UInt32 inDataSize, const void* inPropertyValue)
420 ((CIOSAudioRenderer*)inClientData)->PropertyChanged(inID, inDataSize, inPropertyValue);