1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 /*
3 bqaudiostream
4
5 A small library wrapping various audio file read/write
6 implementations in C++.
7
8 Copyright 2007-2015 Particular Programs Ltd.
9
10 Permission is hereby granted, free of charge, to any person
11 obtaining a copy of this software and associated documentation
12 files (the "Software"), to deal in the Software without
13 restriction, including without limitation the rights to use, copy,
14 modify, merge, publish, distribute, sublicense, and/or sell copies
15 of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be
19 included in all copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
25 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
26 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
29 Except as contained in this notice, the names of Chris Cannam and
30 Particular Programs Ltd shall not be used in advertising or
31 otherwise to promote the sale, use or other dealings in this
32 Software without prior written authorization.
33 */
34
35 #ifdef HAVE_COREAUDIO
36
37 // OS/X system headers don't cope with DEBUG
38 #ifdef DEBUG
39 #undef DEBUG
40 #endif
41
42 #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
43 #include <AudioToolbox/AudioToolbox.h>
44 #include <AudioToolbox/ExtendedAudioFile.h>
45 #else
46 #include "AudioToolbox.h"
47 #include "ExtendedAudioFile.h"
48 #endif
49
50 #include "CoreAudioReadStream.h"
51
52 #include <sstream>
53
54 namespace breakfastquay
55 {
56
57 static vector<string>
getCoreAudioExtensions()58 getCoreAudioExtensions()
59 {
60 vector<string> extensions;
61 extensions.push_back("aiff");
62 extensions.push_back("aif");
63 extensions.push_back("au");
64 extensions.push_back("avi");
65 extensions.push_back("m4a");
66 extensions.push_back("m4b");
67 extensions.push_back("m4p");
68 extensions.push_back("m4v");
69 extensions.push_back("mov");
70 extensions.push_back("mp3");
71 extensions.push_back("mp4");
72 extensions.push_back("wav");
73 return extensions;
74 }
75
76 static
77 AudioReadStreamBuilder<CoreAudioReadStream>
78 coreaudiobuilder(
79 string("http://breakfastquay.com/rdf/turbot/audiostream/CoreAudioReadStream"),
80 getCoreAudioExtensions()
81 );
82
83 class CoreAudioReadStream::D
84 {
85 public:
D()86 D() { }
87
88 ExtAudioFileRef file;
89 AudioBufferList buffer;
90 OSStatus err;
91 AudioStreamBasicDescription asbd;
92 };
93
94 static string
codestr(OSStatus err)95 codestr(OSStatus err)
96 {
97 char text[5];
98 UInt32 uerr = err;
99 text[0] = (uerr >> 24) & 0xff;
100 text[1] = (uerr >> 16) & 0xff;
101 text[2] = (uerr >> 8) & 0xff;
102 text[3] = (uerr) & 0xff;
103 text[4] = '\0';
104 ostringstream os;
105 os << err << " (" << text << ")";
106 return os.str();
107 }
108
CoreAudioReadStream(string path)109 CoreAudioReadStream::CoreAudioReadStream(string path) :
110 m_path(path),
111 m_d(new D)
112 {
113 m_channelCount = 0;
114 m_sampleRate = 0;
115
116 CFURLRef url = CFURLCreateFromFileSystemRepresentation
117 (kCFAllocatorDefault,
118 (const UInt8 *)path.c_str(),
119 (CFIndex)path.size(),
120 false);
121
122 UInt32 propsize;
123 OSStatus noncritical;
124
125 m_d->err = ExtAudioFileOpenURL(url, &m_d->file);
126
127 CFRelease(url);
128
129 if (m_d->err == kAudio_FileNotFoundError) {
130 throw FileNotFound(m_path);
131 }
132
133 if (m_d->err) {
134 m_error = "CoreAudioReadStream: Error opening file: code " + codestr(m_d->err);
135 throw InvalidFileFormat(path, "failed to open audio file");
136 }
137 if (!m_d->file) {
138 m_error = "CoreAudioReadStream: Failed to open file, but no error reported!";
139 throw InvalidFileFormat(path, "failed to open audio file");
140 }
141
142 // Retrieve metadata through the underlying AudioFile API if possible
143
144 AudioFileID audioFile = 0;
145 propsize = sizeof(AudioFileID);
146 noncritical = ExtAudioFileGetProperty
147 (m_d->file, kExtAudioFileProperty_AudioFile, &propsize, &audioFile);
148
149 if (noncritical == noErr) {
150
151 CFDictionaryRef dict = nil;
152 UInt32 dataSize = sizeof(dict);
153 noncritical = AudioFileGetProperty
154 (audioFile, kAudioFilePropertyInfoDictionary, &dataSize, &dict);
155
156 if (noncritical == noErr) {
157
158 CFIndex count = CFDictionaryGetCount(dict);
159 const void **kk = new const void *[count];
160 const void **vv = new const void *[count];
161 CFDictionaryGetKeysAndValues(dict, kk, vv);
162
163 int bufsize = 10240;
164 char *buffer = new char[bufsize];
165
166 for (int i = 0; i < count; ++i) {
167 if (CFGetTypeID(kk[i]) == CFStringGetTypeID() &&
168 CFGetTypeID(vv[i]) == CFStringGetTypeID()) {
169 CFStringRef key = reinterpret_cast<CFStringRef>(kk[i]);
170 CFStringRef value = reinterpret_cast<CFStringRef>(vv[i]);
171 if (CFStringGetCString(key, buffer, bufsize,
172 kCFStringEncodingUTF8)) {
173 string kstr = buffer;
174 if (CFStringGetCString(value, buffer, bufsize,
175 kCFStringEncodingUTF8)) {
176 if (kstr == kAFInfoDictionary_Title) {
177 m_track = buffer;
178 } else if (kstr == kAFInfoDictionary_Artist) {
179 m_artist = buffer;
180 }
181 }
182 }
183 }
184 }
185
186 delete[] buffer;
187 delete[] kk;
188 delete[] vv;
189
190 CFRelease(dict);
191 }
192 }
193
194 propsize = sizeof(AudioStreamBasicDescription);
195 m_d->err = ExtAudioFileGetProperty
196 (m_d->file, kExtAudioFileProperty_FileDataFormat, &propsize, &m_d->asbd);
197
198 if (m_d->err) {
199 m_error = "CoreAudioReadStream: Error in getting basic description: code " + codestr(m_d->err);
200 ExtAudioFileDispose(m_d->file);
201 throw FileOperationFailed(m_path, "get basic description", codestr(m_d->err));
202 }
203
204 m_channelCount = m_d->asbd.mChannelsPerFrame;
205 m_sampleRate = m_d->asbd.mSampleRate;
206
207 m_d->asbd.mSampleRate = getSampleRate();
208 m_d->asbd.mFormatID = kAudioFormatLinearPCM;
209 m_d->asbd.mFormatFlags =
210 kAudioFormatFlagIsFloat |
211 kAudioFormatFlagIsPacked |
212 kAudioFormatFlagsNativeEndian;
213 m_d->asbd.mBitsPerChannel = sizeof(float) * 8;
214 m_d->asbd.mBytesPerFrame = sizeof(float) * m_channelCount;
215 m_d->asbd.mBytesPerPacket = sizeof(float) * m_channelCount;
216 m_d->asbd.mFramesPerPacket = 1;
217 m_d->asbd.mReserved = 0;
218
219 m_d->err = ExtAudioFileSetProperty
220 (m_d->file, kExtAudioFileProperty_ClientDataFormat, propsize, &m_d->asbd);
221
222 if (m_d->err) {
223 m_error = "CoreAudioReadStream: Error in setting client format: code " + codestr(m_d->err);
224 throw FileOperationFailed(m_path, "set client format", codestr(m_d->err));
225 }
226
227 m_d->buffer.mNumberBuffers = 1;
228 m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
229 m_d->buffer.mBuffers[0].mDataByteSize = 0;
230 m_d->buffer.mBuffers[0].mData = 0;
231 }
232
233 size_t
getFrames(size_t count,float * frames)234 CoreAudioReadStream::getFrames(size_t count, float *frames)
235 {
236 if (!m_channelCount) return 0;
237 if (count == 0) return 0;
238
239 m_d->buffer.mBuffers[0].mDataByteSize =
240 sizeof(float) * m_channelCount * count;
241
242 m_d->buffer.mBuffers[0].mData = frames;
243
244 UInt32 framesRead = count;
245
246 m_d->err = ExtAudioFileRead(m_d->file, &framesRead, &m_d->buffer);
247 if (m_d->err) {
248 m_error = "CoreAudioReadStream: Error in decoder: code " + codestr(m_d->err);
249 throw InvalidFileFormat(m_path, "error in decoder");
250 }
251
252 // cerr << "CoreAudioReadStream::getFrames: " << count << " frames requested across " << m_channelCount << " channel(s), " << framesRead << " frames actually read" << std::endl;
253
254 return framesRead;
255 }
256
~CoreAudioReadStream()257 CoreAudioReadStream::~CoreAudioReadStream()
258 {
259 // cerr << "CoreAudioReadStream::~CoreAudioReadStream" << std::endl;
260
261 if (m_channelCount) {
262 ExtAudioFileDispose(m_d->file);
263 }
264
265 m_channelCount = 0;
266
267 delete m_d;
268 }
269
270 }
271
272 #endif
273
274