1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * Copyright (C) 2011, 2014 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 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) && PLATFORM(IOS) 33 34#include "AudioDestinationIOS.h" 35 36#include "AudioIOCallback.h" 37#include "AudioSession.h" 38#include "FloatConversion.h" 39#include "Logging.h" 40#include "Page.h" 41#include "SoftLinking.h" 42#include <AudioToolbox/AudioServices.h> 43#include <WebCore/RuntimeApplicationChecksIOS.h> 44#include <wtf/HashSet.h> 45 46SOFT_LINK_FRAMEWORK(AudioToolbox) 47SOFT_LINK(AudioToolbox, AudioComponentFindNext, AudioComponent, (AudioComponent inComponent, const AudioComponentDescription *inDesc), (inComponent, inDesc)) 48SOFT_LINK(AudioToolbox, AudioComponentInstanceDispose, OSStatus, (AudioComponentInstance inInstance), (inInstance)) 49SOFT_LINK(AudioToolbox, AudioComponentInstanceNew, OSStatus, (AudioComponent inComponent, AudioComponentInstance *outInstance), (inComponent, outInstance)) 50SOFT_LINK(AudioToolbox, AudioOutputUnitStart, OSStatus, (AudioUnit ci), (ci)) 51SOFT_LINK(AudioToolbox, AudioOutputUnitStop, OSStatus, (AudioUnit ci), (ci)) 52SOFT_LINK(AudioToolbox, AudioUnitAddPropertyListener, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void *inProcUserData), (inUnit, inID, inProc, inProcUserData)) 53SOFT_LINK(AudioToolbox, AudioUnitGetProperty, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void *outData, UInt32 *ioDataSize), (inUnit, inID, inScope, inElement, outData, ioDataSize)) 54SOFT_LINK(AudioToolbox, AudioUnitInitialize, OSStatus, (AudioUnit inUnit), (inUnit)) 55SOFT_LINK(AudioToolbox, AudioUnitSetProperty, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void *inData, UInt32 inDataSize), (inUnit, inID, inScope, inElement, inData, inDataSize)) 56 57namespace WebCore { 58 59const int kRenderBufferSize = 128; 60const int kPreferredBufferSize = 256; 61 62typedef HashSet<AudioDestinationIOS*> AudioDestinationSet; 63static AudioDestinationSet& audioDestinations() 64{ 65 DEPRECATED_DEFINE_STATIC_LOCAL(AudioDestinationSet, audioDestinationSet, ()); 66 return audioDestinationSet; 67} 68 69// Factory method: iOS-implementation 70std::unique_ptr<AudioDestination> AudioDestination::create(AudioIOCallback& callback, const String&, unsigned numberOfInputChannels, unsigned numberOfOutputChannels, float sampleRate) 71{ 72 // FIXME: make use of inputDeviceId as appropriate. 73 74 // FIXME: Add support for local/live audio input. 75 if (numberOfInputChannels) 76 LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled input channels", numberOfInputChannels, numberOfOutputChannels, sampleRate); 77 78 // FIXME: Add support for multi-channel (> stereo) output. 79 if (numberOfOutputChannels != 2) 80 LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled output channels", numberOfInputChannels, numberOfOutputChannels, sampleRate); 81 82 return std::make_unique<AudioDestinationIOS>(callback, sampleRate); 83} 84 85float AudioDestination::hardwareSampleRate() 86{ 87 return AudioSession::sharedSession().sampleRate(); 88} 89 90unsigned long AudioDestination::maxChannelCount() 91{ 92 // FIXME: query the default audio hardware device to return the actual number 93 // of channels of the device. Also see corresponding FIXME in create(). 94 // There is a small amount of code which assumes stereo in AudioDestinationIOS which 95 // can be upgraded. 96 return 0; 97} 98 99AudioDestinationIOS::AudioDestinationIOS(AudioIOCallback& callback, double sampleRate) 100 : m_outputUnit(0) 101 , m_callback(callback) 102 , m_renderBus(AudioBus::create(2, kRenderBufferSize, false)) 103 , m_mediaSession(MediaSession::create(*this)) 104 , m_sampleRate(sampleRate) 105 , m_isPlaying(false) 106{ 107 audioDestinations().add(this); 108 109 // Open and initialize DefaultOutputUnit 110 AudioComponent comp; 111 AudioComponentDescription desc; 112 113 desc.componentType = kAudioUnitType_Output; 114 desc.componentSubType = kAudioUnitSubType_RemoteIO; 115 desc.componentManufacturer = kAudioUnitManufacturer_Apple; 116 desc.componentFlags = 0; 117 desc.componentFlagsMask = 0; 118 comp = AudioComponentFindNext(0, &desc); 119 120 ASSERT(comp); 121 122 OSStatus result = AudioComponentInstanceNew(comp, &m_outputUnit); 123 ASSERT(!result); 124 125 UInt32 flag = 1; 126 result = AudioUnitSetProperty(m_outputUnit, 127 kAudioOutputUnitProperty_EnableIO, 128 kAudioUnitScope_Output, 129 0, 130 &flag, 131 sizeof(flag)); 132 ASSERT(!result); 133 134 result = AudioUnitAddPropertyListener(m_outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, frameSizeChangedProc, this); 135 ASSERT(!result); 136 137 result = AudioUnitInitialize(m_outputUnit); 138 ASSERT(!result); 139 140 configure(); 141} 142 143AudioDestinationIOS::~AudioDestinationIOS() 144{ 145 audioDestinations().remove(this); 146 147 if (m_outputUnit) 148 AudioComponentInstanceDispose(m_outputUnit); 149} 150 151void AudioDestinationIOS::configure() 152{ 153 // Set render callback 154 AURenderCallbackStruct input; 155 input.inputProc = inputProc; 156 input.inputProcRefCon = this; 157 OSStatus result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(input)); 158 ASSERT(!result); 159 160 // Set stream format 161 AudioStreamBasicDescription streamFormat; 162 163 UInt32 size = sizeof(AudioStreamBasicDescription); 164 result = AudioUnitGetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, (void*)&streamFormat, &size); 165 ASSERT(!result); 166 167 const int bytesPerFloat = sizeof(Float32); 168 const int bitsPerByte = 8; 169 streamFormat.mSampleRate = m_sampleRate; 170 streamFormat.mFormatID = kAudioFormatLinearPCM; 171 streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; 172 streamFormat.mBytesPerPacket = bytesPerFloat; 173 streamFormat.mFramesPerPacket = 1; 174 streamFormat.mBytesPerFrame = bytesPerFloat; 175 streamFormat.mChannelsPerFrame = 2; 176 streamFormat.mBitsPerChannel = bitsPerByte * bytesPerFloat; 177 178 result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription)); 179 ASSERT(!result); 180 181 AudioSession::sharedSession().setPreferredBufferSize(kPreferredBufferSize); 182} 183 184void AudioDestinationIOS::start() 185{ 186 LOG(Media, "AudioDestinationIOS::start"); 187 if (!m_mediaSession->clientWillBeginPlayback()) { 188 LOG(Media, " returning because of interruption"); 189 return; 190 } 191 192 OSStatus result = AudioOutputUnitStart(m_outputUnit); 193 if (!result) 194 m_isPlaying = true; 195} 196 197void AudioDestinationIOS::stop() 198{ 199 LOG(Media, "AudioDestinationIOS::stop"); 200 if (!m_mediaSession->clientWillPausePlayback()) { 201 LOG(Media, " returning because of interruption"); 202 return; 203 } 204 205 OSStatus result = AudioOutputUnitStop(m_outputUnit); 206 if (!result) 207 m_isPlaying = false; 208} 209 210// Pulls on our provider to get rendered audio stream. 211OSStatus AudioDestinationIOS::render(UInt32 numberOfFrames, AudioBufferList* ioData) 212{ 213 AudioBuffer* buffers = ioData->mBuffers; 214 for (UInt32 frameOffset = 0; frameOffset + kRenderBufferSize <= numberOfFrames; frameOffset += kRenderBufferSize) { 215 UInt32 remainingFrames = std::min<UInt32>(kRenderBufferSize, numberOfFrames - frameOffset); 216 for (UInt32 i = 0; i < ioData->mNumberBuffers; ++i) { 217 UInt32 bytesPerFrame = buffers[i].mDataByteSize / numberOfFrames; 218 UInt32 byteOffset = frameOffset * bytesPerFrame; 219 float* memory = (float*)((char*)buffers[i].mData + byteOffset); 220 m_renderBus->setChannelMemory(i, memory, remainingFrames); 221 } 222 m_callback.render(0, m_renderBus.get(), remainingFrames); 223 } 224 225 return noErr; 226} 227 228// DefaultOutputUnit callback 229OSStatus AudioDestinationIOS::inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 /*busNumber*/, UInt32 numberOfFrames, AudioBufferList* ioData) 230{ 231 AudioDestinationIOS* audioOutput = static_cast<AudioDestinationIOS*>(userData); 232 return audioOutput->render(numberOfFrames, ioData); 233} 234 235void AudioDestinationIOS::frameSizeChangedProc(void *inRefCon, AudioUnit, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement) 236{ 237 AudioDestinationIOS* audioOutput = static_cast<AudioDestinationIOS*>(inRefCon); 238 UInt32 bufferSize = 0; 239 UInt32 dataSize = sizeof(bufferSize); 240 AudioUnitGetProperty(audioOutput->m_outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, (void*)&bufferSize, &dataSize); 241 fprintf(stderr, ">>>> frameSizeChanged = %lu\n", static_cast<unsigned long>(bufferSize)); 242} 243 244} // namespace WebCore 245 246#endif // ENABLE(WEB_AUDIO) && PLATFORM(IOS) 247 248