1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30
31#if ENABLE(WEB_AUDIO)
32
33#include "AudioDestinationMac.h"
34
35#include "AudioSourceProvider.h"
36#include <CoreAudio/AudioHardware.h>
37
38namespace WebCore {
39
40const int kBufferSize = 128;
41
42// Factory method: Mac-implementation
43PassOwnPtr<AudioDestination> AudioDestination::create(AudioSourceProvider& provider, double sampleRate)
44{
45    return adoptPtr(new AudioDestinationMac(provider, sampleRate));
46}
47
48double AudioDestination::hardwareSampleRate()
49{
50    // Determine the default output device's sample-rate.
51    AudioDeviceID deviceID = kAudioDeviceUnknown;
52    UInt32 infoSize = sizeof(deviceID);
53
54    AudioObjectPropertyAddress defaultOutputDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
55    OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultOutputDeviceAddress, 0, 0, &infoSize, (void*)&deviceID);
56    if (result)
57        return 0.0; // error
58
59    Float64 nominalSampleRate;
60    infoSize = sizeof(Float64);
61
62    AudioObjectPropertyAddress nominalSampleRateAddress = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
63    result = AudioObjectGetPropertyData(deviceID, &nominalSampleRateAddress, 0, 0, &infoSize, (void*)&nominalSampleRate);
64    if (result)
65        return 0.0; // error
66
67    return nominalSampleRate;
68}
69
70AudioDestinationMac::AudioDestinationMac(AudioSourceProvider& provider, double sampleRate)
71    : m_outputUnit(0)
72    , m_provider(provider)
73    , m_renderBus(2, kBufferSize, false)
74    , m_sampleRate(sampleRate)
75    , m_isPlaying(false)
76{
77    // Open and initialize DefaultOutputUnit
78    Component comp;
79    ComponentDescription desc;
80
81    desc.componentType = kAudioUnitType_Output;
82    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
83    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
84    desc.componentFlags = 0;
85    desc.componentFlagsMask = 0;
86    comp = FindNextComponent(0, &desc);
87
88    ASSERT(comp);
89
90    OSStatus result = OpenAComponent(comp, &m_outputUnit);
91    ASSERT(!result);
92
93    result = AudioUnitInitialize(m_outputUnit);
94    ASSERT(!result);
95
96    configure();
97}
98
99AudioDestinationMac::~AudioDestinationMac()
100{
101    if (m_outputUnit)
102        CloseComponent(m_outputUnit);
103}
104
105void AudioDestinationMac::configure()
106{
107    // Set render callback
108    AURenderCallbackStruct input;
109    input.inputProc = inputProc;
110    input.inputProcRefCon = this;
111    OSStatus result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &input, sizeof(input));
112    ASSERT(!result);
113
114    // Set stream format
115    AudioStreamBasicDescription streamFormat;
116    streamFormat.mSampleRate = m_sampleRate;
117    streamFormat.mFormatID = kAudioFormatLinearPCM;
118    streamFormat.mFormatFlags = kAudioFormatFlagsCanonical | kAudioFormatFlagIsNonInterleaved;
119    streamFormat.mBitsPerChannel = 8 * sizeof(AudioSampleType);
120    streamFormat.mChannelsPerFrame = 2;
121    streamFormat.mFramesPerPacket = 1;
122    streamFormat.mBytesPerPacket = sizeof(AudioSampleType);
123    streamFormat.mBytesPerFrame = sizeof(AudioSampleType);
124
125    result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription));
126    ASSERT(!result);
127
128    // Set the buffer frame size.
129    UInt32 bufferSize = kBufferSize;
130    result = AudioUnitSetProperty(m_outputUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Output, 0, (void*)&bufferSize, sizeof(bufferSize));
131    ASSERT(!result);
132}
133
134void AudioDestinationMac::start()
135{
136    OSStatus result = AudioOutputUnitStart(m_outputUnit);
137
138    if (!result)
139        m_isPlaying = true;
140}
141
142void AudioDestinationMac::stop()
143{
144    OSStatus result = AudioOutputUnitStop(m_outputUnit);
145
146    if (!result)
147        m_isPlaying = false;
148}
149
150// Pulls on our provider to get rendered audio stream.
151OSStatus AudioDestinationMac::render(UInt32 numberOfFrames, AudioBufferList* ioData)
152{
153    AudioBuffer* buffers = ioData->mBuffers;
154    m_renderBus.setChannelMemory(0, (float*)buffers[0].mData, numberOfFrames);
155    m_renderBus.setChannelMemory(1, (float*)buffers[1].mData, numberOfFrames);
156
157    m_provider.provideInput(&m_renderBus, numberOfFrames);
158
159    return noErr;
160}
161
162// DefaultOutputUnit callback
163OSStatus AudioDestinationMac::inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 /*busNumber*/, UInt32 numberOfFrames, AudioBufferList* ioData)
164{
165    AudioDestinationMac* audioOutput = static_cast<AudioDestinationMac*>(userData);
166    return audioOutput->render(numberOfFrames, ioData);
167}
168
169} // namespace WebCore
170
171#endif // ENABLE(WEB_AUDIO)
172