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