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 "AudioFileReaderMac.h"
34
35#include "AudioBus.h"
36#include "AudioFileReader.h"
37#include <CoreFoundation/CoreFoundation.h>
38#include <CoreServices/CoreServices.h>
39
40namespace WebCore {
41
42static AudioBufferList* createAudioBufferList(size_t numberOfBuffers)
43{
44    size_t bufferListSize = sizeof(AudioBufferList) - sizeof(AudioBuffer);
45    bufferListSize += numberOfBuffers * sizeof(AudioBuffer);
46
47    AudioBufferList* bufferList = static_cast<AudioBufferList*>(calloc(1, bufferListSize));
48    if (bufferList)
49        bufferList->mNumberBuffers = numberOfBuffers;
50
51    return bufferList;
52}
53
54static void destroyAudioBufferList(AudioBufferList* bufferList)
55{
56    free(bufferList);
57}
58
59AudioFileReader::AudioFileReader(const char* filePath)
60    : m_data(0)
61    , m_dataSize(0)
62    , m_filePath(filePath)
63    , m_audioFileID(0)
64    , m_extAudioFileRef(0)
65{
66    FSRef fsref;
67    OSStatus result = FSPathMakeRef((UInt8*)filePath, &fsref, 0);
68    if (result != noErr)
69        return;
70
71    CFURLRef urlRef = CFURLCreateFromFSRef(0, &fsref);
72    if (!urlRef)
73        return;
74
75    ExtAudioFileOpenURL(urlRef, &m_extAudioFileRef);
76
77    if (urlRef)
78        CFRelease(urlRef);
79}
80
81AudioFileReader::AudioFileReader(const void* data, size_t dataSize)
82    : m_data(data)
83    , m_dataSize(dataSize)
84    , m_filePath(0)
85    , m_audioFileID(0)
86    , m_extAudioFileRef(0)
87{
88    OSStatus result = AudioFileOpenWithCallbacks(this, readProc, 0, getSizeProc, 0, 0, &m_audioFileID);
89
90    if (result != noErr)
91        return;
92
93    result = ExtAudioFileWrapAudioFileID(m_audioFileID, false, &m_extAudioFileRef);
94    if (result != noErr)
95        m_extAudioFileRef = 0;
96}
97
98AudioFileReader::~AudioFileReader()
99{
100    if (m_extAudioFileRef)
101        ExtAudioFileDispose(m_extAudioFileRef);
102
103    m_extAudioFileRef = 0;
104
105    if (m_audioFileID)
106        AudioFileClose(m_audioFileID);
107
108    m_audioFileID = 0;
109}
110
111OSStatus AudioFileReader::readProc(void* clientData, SInt64 position, UInt32 requestCount, void* buffer, UInt32* actualCount)
112{
113    AudioFileReader* audioFileReader = static_cast<AudioFileReader*>(clientData);
114
115    size_t dataSize = audioFileReader->dataSize();
116    const void* data = audioFileReader->data();
117    size_t bytesToRead = 0;
118
119    if (static_cast<UInt64>(position) < dataSize) {
120        size_t bytesAvailable = dataSize - static_cast<size_t>(position);
121        bytesToRead = requestCount <= bytesAvailable ? requestCount : bytesAvailable;
122        memcpy(buffer, static_cast<const char*>(data) + position, bytesToRead);
123    } else
124        bytesToRead = 0;
125
126    if (actualCount)
127        *actualCount = bytesToRead;
128
129    return noErr;
130}
131
132SInt64 AudioFileReader::getSizeProc(void* clientData)
133{
134    AudioFileReader* audioFileReader = static_cast<AudioFileReader*>(clientData);
135    return audioFileReader->dataSize();
136}
137
138PassOwnPtr<AudioBus> AudioFileReader::createBus(double sampleRate, bool mixToMono)
139{
140    if (!m_extAudioFileRef)
141        return 0;
142
143    // Get file's data format
144    UInt32 size = sizeof(m_fileDataFormat);
145    OSStatus result = ExtAudioFileGetProperty(m_extAudioFileRef, kExtAudioFileProperty_FileDataFormat, &size, &m_fileDataFormat);
146    if (result != noErr)
147        return 0;
148
149    // Number of channels
150    size_t numberOfChannels = m_fileDataFormat.mChannelsPerFrame;
151
152    // Number of frames
153    SInt64 numberOfFrames64 = 0;
154    size = sizeof(numberOfFrames64);
155    result = ExtAudioFileGetProperty(m_extAudioFileRef, kExtAudioFileProperty_FileLengthFrames, &size, &numberOfFrames64);
156    if (result != noErr)
157        return 0;
158
159    // Sample-rate
160    double fileSampleRate = m_fileDataFormat.mSampleRate;
161
162    // Make client format same number of channels as file format, but tweak a few things.
163    // Client format will be linear PCM (canonical), and potentially change sample-rate.
164    m_clientDataFormat = m_fileDataFormat;
165
166    m_clientDataFormat.mFormatID = kAudioFormatLinearPCM;
167    m_clientDataFormat.mFormatFlags = kAudioFormatFlagsCanonical;
168    m_clientDataFormat.mBitsPerChannel = 8 * sizeof(AudioSampleType);
169    m_clientDataFormat.mChannelsPerFrame = numberOfChannels;
170    m_clientDataFormat.mFramesPerPacket = 1;
171    m_clientDataFormat.mBytesPerPacket = sizeof(AudioSampleType);
172    m_clientDataFormat.mBytesPerFrame = sizeof(AudioSampleType);
173    m_clientDataFormat.mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
174
175    if (sampleRate)
176        m_clientDataFormat.mSampleRate = sampleRate;
177
178    result = ExtAudioFileSetProperty(m_extAudioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &m_clientDataFormat);
179    if (result != noErr)
180        return 0;
181
182    // Change numberOfFrames64 to destination sample-rate
183    numberOfFrames64 = numberOfFrames64 * (m_clientDataFormat.mSampleRate / fileSampleRate);
184    size_t numberOfFrames = static_cast<size_t>(numberOfFrames64);
185
186    size_t busChannelCount = mixToMono ? 1 : numberOfChannels;
187
188    // Create AudioBus where we'll put the PCM audio data
189    OwnPtr<AudioBus> audioBus = adoptPtr(new AudioBus(busChannelCount, numberOfFrames));
190    audioBus->setSampleRate(m_clientDataFormat.mSampleRate); // save for later
191
192    // Only allocated in the mixToMono case
193    AudioFloatArray bufL;
194    AudioFloatArray bufR;
195    float* bufferL = 0;
196    float* bufferR = 0;
197
198    // Setup AudioBufferList in preparation for reading
199    AudioBufferList* bufferList = createAudioBufferList(numberOfChannels);
200
201    if (mixToMono && numberOfChannels == 2) {
202        bufL.resize(numberOfFrames);
203        bufR.resize(numberOfFrames);
204        bufferL = bufL.data();
205        bufferR = bufR.data();
206
207        bufferList->mBuffers[0].mNumberChannels = 1;
208        bufferList->mBuffers[0].mDataByteSize = numberOfFrames * sizeof(float);
209        bufferList->mBuffers[0].mData = bufferL;
210
211        bufferList->mBuffers[1].mNumberChannels = 1;
212        bufferList->mBuffers[1].mDataByteSize = numberOfFrames * sizeof(float);
213        bufferList->mBuffers[1].mData = bufferR;
214    } else {
215        ASSERT(!mixToMono || numberOfChannels == 1);
216
217        // for True-stereo (numberOfChannels == 4)
218        for (size_t i = 0; i < numberOfChannels; ++i) {
219            bufferList->mBuffers[i].mNumberChannels = 1;
220            bufferList->mBuffers[i].mDataByteSize = numberOfFrames * sizeof(float);
221            bufferList->mBuffers[i].mData = audioBus->channel(i)->data();
222        }
223    }
224
225    // Read from the file (or in-memory version)
226    UInt32 framesToRead = numberOfFrames;
227    result = ExtAudioFileRead(m_extAudioFileRef, &framesToRead, bufferList);
228    if (result != noErr)
229        return 0;
230
231    if (mixToMono && numberOfChannels == 2) {
232        // Mix stereo down to mono
233        float* destL = audioBus->channel(0)->data();
234        for (size_t i = 0; i < numberOfFrames; i++)
235            destL[i] = 0.5f * (bufferL[i] + bufferR[i]);
236    }
237
238    // Cleanup
239    destroyAudioBufferList(bufferList);
240
241    return audioBus.release();
242}
243
244PassOwnPtr<AudioBus> createBusFromAudioFile(const char* filePath, bool mixToMono, double sampleRate)
245{
246    AudioFileReader reader(filePath);
247    return reader.createBus(sampleRate, mixToMono);
248}
249
250PassOwnPtr<AudioBus> createBusFromInMemoryAudioFile(const void* data, size_t dataSize, bool mixToMono, double sampleRate)
251{
252    AudioFileReader reader(data, dataSize);
253    return reader.createBus(sampleRate, mixToMono);
254}
255
256} // WebCore
257
258#endif // ENABLE(WEB_AUDIO)
259