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