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