1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * Copyright (C) 2011 Apple Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include "config.h" 31 32#if ENABLE(WEB_AUDIO) 33 34#if PLATFORM(IOS) 35 36#include "AudioDestinationIOS.h" 37 38#include "AudioIOCallback.h" 39#include "AudioSession.h" 40#include "FloatConversion.h" 41#include "Logging.h" 42#include "Page.h" 43#include "SoftLinking.h" 44#include <AudioToolbox/AudioServices.h> 45#include <WebCore/RuntimeApplicationChecksIOS.h> 46#include <wtf/HashSet.h> 47 48SOFT_LINK_FRAMEWORK(AudioToolbox) 49SOFT_LINK(AudioToolbox, AudioComponentFindNext, AudioComponent, (AudioComponent inComponent, const AudioComponentDescription *inDesc), (inComponent, inDesc)) 50SOFT_LINK(AudioToolbox, AudioComponentInstanceDispose, OSStatus, (AudioComponentInstance inInstance), (inInstance)) 51SOFT_LINK(AudioToolbox, AudioComponentInstanceNew, OSStatus, (AudioComponent inComponent, AudioComponentInstance *outInstance), (inComponent, outInstance)) 52SOFT_LINK(AudioToolbox, AudioOutputUnitStart, OSStatus, (AudioUnit ci), (ci)) 53SOFT_LINK(AudioToolbox, AudioOutputUnitStop, OSStatus, (AudioUnit ci), (ci)) 54SOFT_LINK(AudioToolbox, AudioUnitAddPropertyListener, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void *inProcUserData), (inUnit, inID, inProc, inProcUserData)) 55SOFT_LINK(AudioToolbox, AudioUnitGetProperty, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void *outData, UInt32 *ioDataSize), (inUnit, inID, inScope, inElement, outData, ioDataSize)) 56SOFT_LINK(AudioToolbox, AudioUnitInitialize, OSStatus, (AudioUnit inUnit), (inUnit)) 57SOFT_LINK(AudioToolbox, AudioUnitSetProperty, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void *inData, UInt32 inDataSize), (inUnit, inID, inScope, inElement, inData, inDataSize)) 58 59namespace WebCore { 60 61const int kRenderBufferSize = 128; 62const int kPreferredBufferSize = 256; 63 64typedef HashSet<AudioDestinationIOS*> AudioDestinationSet; 65static AudioDestinationSet& audioDestinations() 66{ 67 DEFINE_STATIC_LOCAL(AudioDestinationSet, audioDestinationSet, ()); 68 return audioDestinationSet; 69} 70 71// Factory method: iOS-implementation 72PassOwnPtr<AudioDestination> AudioDestination::create(AudioIOCallback& callback, const String&, unsigned numberOfInputChannels, unsigned numberOfOutputChannels, float sampleRate) 73{ 74 // FIXME: make use of inputDeviceId as appropriate. 75 76 // FIXME: Add support for local/live audio input. 77 if (numberOfInputChannels) 78 LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled input channels", numberOfInputChannels, numberOfOutputChannels, sampleRate); 79 80 // FIXME: Add support for multi-channel (> stereo) output. 81 if (numberOfOutputChannels != 2) 82 LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled output channels", numberOfInputChannels, numberOfOutputChannels, sampleRate); 83 84 return adoptPtr(new AudioDestinationIOS(callback, sampleRate)); 85} 86 87float AudioDestination::hardwareSampleRate() 88{ 89 return AudioSession::sharedSession().sampleRate(); 90} 91 92unsigned long AudioDestination::maxChannelCount() 93{ 94 // FIXME: query the default audio hardware device to return the actual number 95 // of channels of the device. Also see corresponding FIXME in create(). 96 // There is a small amount of code which assumes stereo in AudioDestinationIOS which 97 // can be upgraded. 98 return 0; 99} 100 101AudioDestinationIOS::AudioDestinationIOS(AudioIOCallback& callback, double sampleRate) 102 : m_outputUnit(0) 103 , m_callback(callback) 104 , m_renderBus(AudioBus::create(2, kRenderBufferSize, false)) 105 , m_sampleRate(sampleRate) 106 , m_isPlaying(false) 107 , m_interruptedOnPlayback(false) 108{ 109 AudioSession& session = AudioSession::sharedSession(); 110 session.addListener(this); 111 session.setCategory(AudioSession::AmbientSound); 112 113 audioDestinations().add(this); 114 if (audioDestinations().size() == 1) 115 session.setActive(1); 116 117 // Open and initialize DefaultOutputUnit 118 AudioComponent comp; 119 AudioComponentDescription desc; 120 121 desc.componentType = kAudioUnitType_Output; 122 desc.componentSubType = kAudioUnitSubType_RemoteIO; 123 desc.componentManufacturer = kAudioUnitManufacturer_Apple; 124 desc.componentFlags = 0; 125 desc.componentFlagsMask = 0; 126 comp = AudioComponentFindNext(0, &desc); 127 128 ASSERT(comp); 129 130 OSStatus result = AudioComponentInstanceNew(comp, &m_outputUnit); 131 ASSERT(!result); 132 133 UInt32 flag = 1; 134 result = AudioUnitSetProperty(m_outputUnit, 135 kAudioOutputUnitProperty_EnableIO, 136 kAudioUnitScope_Output, 137 0, 138 &flag, 139 sizeof(flag)); 140 ASSERT(!result); 141 142 result = AudioUnitAddPropertyListener(m_outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, frameSizeChangedProc, this); 143 ASSERT(!result); 144 145 result = AudioUnitInitialize(m_outputUnit); 146 ASSERT(!result); 147 148 configure(); 149} 150 151AudioDestinationIOS::~AudioDestinationIOS() 152{ 153 audioDestinations().remove(this); 154 if (!audioDestinations().size()) 155 AudioSession::sharedSession().setActive(0); 156 157 if (m_outputUnit) 158 AudioComponentInstanceDispose(m_outputUnit); 159} 160 161void AudioDestinationIOS::configure() 162{ 163 // Set render callback 164 AURenderCallbackStruct input; 165 input.inputProc = inputProc; 166 input.inputProcRefCon = this; 167 OSStatus result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(input)); 168 ASSERT(!result); 169 170 // Set stream format 171 AudioStreamBasicDescription streamFormat; 172 173 UInt32 size = sizeof(AudioStreamBasicDescription); 174 result = AudioUnitGetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, (void*)&streamFormat, &size); 175 ASSERT(!result); 176 177 const int bytesPerFloat = sizeof(Float32); 178 const int bitsPerByte = 8; 179 streamFormat.mSampleRate = m_sampleRate; 180 streamFormat.mFormatID = kAudioFormatLinearPCM; 181 streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; 182 streamFormat.mBytesPerPacket = bytesPerFloat; 183 streamFormat.mFramesPerPacket = 1; 184 streamFormat.mBytesPerFrame = bytesPerFloat; 185 streamFormat.mChannelsPerFrame = 2; 186 streamFormat.mBitsPerChannel = bitsPerByte * bytesPerFloat; 187 188 result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription)); 189 ASSERT(!result); 190 191 AudioSession::sharedSession().setPreferredBufferSize(kPreferredBufferSize); 192} 193 194void AudioDestinationIOS::start() 195{ 196 OSStatus result = AudioOutputUnitStart(m_outputUnit); 197 198 if (!result) 199 m_isPlaying = true; 200} 201 202void AudioDestinationIOS::stop() 203{ 204 OSStatus result = AudioOutputUnitStop(m_outputUnit); 205 206 if (!result) 207 m_isPlaying = false; 208} 209 210void AudioDestinationIOS::beganAudioInterruption() 211{ 212 if (!m_isPlaying) 213 return; 214 215 stop(); 216 m_interruptedOnPlayback = true; 217} 218 219void AudioDestinationIOS::endedAudioInterruption() 220{ 221 if (!m_interruptedOnPlayback) 222 return; 223 224 m_interruptedOnPlayback = false; 225 start(); 226} 227 228// Pulls on our provider to get rendered audio stream. 229OSStatus AudioDestinationIOS::render(UInt32 numberOfFrames, AudioBufferList* ioData) 230{ 231 AudioBuffer* buffers = ioData->mBuffers; 232 for (UInt32 frameOffset = 0; frameOffset + kRenderBufferSize <= numberOfFrames; frameOffset += kRenderBufferSize) { 233 UInt32 remainingFrames = std::min<UInt32>(kRenderBufferSize, numberOfFrames - frameOffset); 234 for (UInt32 i = 0; i < ioData->mNumberBuffers; ++i) { 235 UInt32 bytesPerFrame = buffers[i].mDataByteSize / numberOfFrames; 236 UInt32 byteOffset = frameOffset * bytesPerFrame; 237 float* memory = (float*)((char*)buffers[i].mData + byteOffset); 238 m_renderBus->setChannelMemory(i, memory, remainingFrames); 239 } 240 m_callback.render(0, m_renderBus.get(), remainingFrames); 241 } 242 243 return noErr; 244} 245 246// DefaultOutputUnit callback 247OSStatus AudioDestinationIOS::inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 /*busNumber*/, UInt32 numberOfFrames, AudioBufferList* ioData) 248{ 249 AudioDestinationIOS* audioOutput = static_cast<AudioDestinationIOS*>(userData); 250 return audioOutput->render(numberOfFrames, ioData); 251} 252 253void AudioDestinationIOS::frameSizeChangedProc(void *inRefCon, AudioUnit, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement) 254{ 255 AudioDestinationIOS* audioOutput = static_cast<AudioDestinationIOS*>(inRefCon); 256 UInt32 bufferSize = 0; 257 UInt32 dataSize = sizeof(bufferSize); 258 AudioUnitGetProperty(audioOutput->m_outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, (void*)&bufferSize, &dataSize); 259 fprintf(stderr, ">>>> frameSizeChanged = %lu\n", static_cast<unsigned long>(bufferSize)); 260} 261 262} // namespace WebCore 263 264#endif // PLATFORM(IOS) 265 266#endif // ENABLE(WEB_AUDIO) 267