![]() |
Mixxx
|
00001 00007 /*************************************************************************** 00008 * * 00009 * This program is free software; you can redistribute it and/or modify * 00010 * it under the terms of the GNU General Public License as published by * 00011 * the Free Software Foundation; either version 2 of the License, or * 00012 * (at your option) any later version. * 00013 * * 00014 ***************************************************************************/ 00015 00016 #include <QtDebug> 00017 #include <QUrl> 00018 #include <taglib/mpegfile.h> 00019 #include <taglib/mp4file.h> 00020 00021 #include "soundsourcecoreaudio.h" 00022 00023 SoundSourceCoreAudio::SoundSourceCoreAudio(QString filename) 00024 : Mixxx::SoundSource(filename) 00025 , m_file(filename) 00026 , m_samples(0) 00027 , m_headerFrames(0) 00028 { 00029 } 00030 00031 SoundSourceCoreAudio::~SoundSourceCoreAudio() { 00032 ExtAudioFileDispose(m_audioFile); 00033 00034 } 00035 00036 // soundsource overrides 00037 int SoundSourceCoreAudio::open() { 00038 //m_file.open(QIODevice::ReadOnly); 00039 00040 //Open the audio file. 00041 OSStatus err; 00042 00043 //QUrl blah(m_qFilename); 00044 QString qurlStr = m_qFilename;//blah.toString(); 00045 qDebug() << qurlStr; 00046 00048 CFStringRef urlStr = CFStringCreateWithCharacters(0, 00049 reinterpret_cast<const UniChar *>( 00050 qurlStr.unicode()), qurlStr.size()); 00051 CFURLRef urlRef = CFURLCreateWithFileSystemPath(NULL, urlStr, kCFURLPOSIXPathStyle, false); 00052 err = ExtAudioFileOpenURL(urlRef, &m_audioFile); 00053 CFRelease(urlStr); 00054 CFRelease(urlRef); 00055 00064 if (err != noErr) 00065 { 00066 qDebug() << "SSCA: Error opening file."; 00067 return ERR; 00068 } 00069 00070 // get the input file format 00071 CAStreamBasicDescription inputFormat; 00072 UInt32 size = sizeof(inputFormat); 00073 m_inputFormat = inputFormat; 00074 err = ExtAudioFileGetProperty(m_audioFile, kExtAudioFileProperty_FileDataFormat, &size, &inputFormat); 00075 if (err != noErr) 00076 { 00077 qDebug() << "SSCA: Error getting file format"; 00078 return ERR; 00079 } 00080 00081 //Debugging: 00082 //printf ("Source File format: "); inputFormat.Print(); 00083 //printf ("Dest File format: "); outputFormat.Print(); 00084 00085 00086 // create the output format 00087 CAStreamBasicDescription outputFormat; 00088 bzero(&outputFormat, sizeof(AudioStreamBasicDescription)); 00089 outputFormat.mFormatID = kAudioFormatLinearPCM; 00090 outputFormat.mSampleRate = inputFormat.mSampleRate; 00091 outputFormat.mChannelsPerFrame = 2; 00092 outputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger; 00093 00094 /* 00095 switch(inputFormat.mBitsPerChannel) { 00096 case 16: 00097 outputFormat.mFormatFlags = kAppleLosslessFormatFlag_16BitSourceData; 00098 break; 00099 case 20: 00100 outputFormat.mFormatFlags = kAppleLosslessFormatFlag_20BitSourceData; 00101 break; 00102 case 24: 00103 outputFormat.mFormatFlags = kAppleLosslessFormatFlag_24BitSourceData; 00104 break; 00105 case 32: 00106 outputFormat.mFormatFlags = kAppleLosslessFormatFlag_32BitSourceData; 00107 break; 00108 }*/ 00109 00110 // get and set the client format - it should be lpcm 00111 CAStreamBasicDescription clientFormat = (inputFormat.mFormatID == kAudioFormatLinearPCM ? inputFormat : outputFormat); 00112 clientFormat.mBytesPerPacket = 4; 00113 clientFormat.mFramesPerPacket = 1; 00114 clientFormat.mBytesPerFrame = 4; 00115 clientFormat.mChannelsPerFrame = 2; 00116 clientFormat.mBitsPerChannel = 16; 00117 clientFormat.mReserved = 0; 00118 m_clientFormat = clientFormat; 00119 size = sizeof(clientFormat); 00120 00121 err = ExtAudioFileSetProperty(m_audioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat); 00122 if (err != noErr) 00123 { 00124 qDebug() << "SSCA: Error setting file property"; 00125 return ERR; 00126 } 00127 00128 //Set m_iChannels and m_samples; 00129 m_iChannels = clientFormat.NumberChannels(); 00130 00131 //get the total length in frames of the audio file - copypasta: http://discussions.apple.com/thread.jspa?threadID=2364583&tstart=47 00132 UInt32 dataSize; 00133 SInt64 totalFrameCount; 00134 dataSize = sizeof(totalFrameCount); //XXX: This looks sketchy to me - Albert 00135 err = ExtAudioFileGetProperty(m_audioFile, kExtAudioFileProperty_FileLengthFrames, &dataSize, &totalFrameCount); 00136 if (err != noErr) 00137 { 00138 qDebug() << "SSCA: Error getting number of frames"; 00139 return ERR; 00140 } 00141 00142 // 00143 // WORKAROUND for bug in ExtFileAudio 00144 // 00145 00146 AudioConverterRef acRef; 00147 UInt32 acrsize=sizeof(AudioConverterRef); 00148 err = ExtAudioFileGetProperty(m_audioFile, kExtAudioFileProperty_AudioConverter, &acrsize, &acRef); 00149 //_ThrowExceptionIfErr(@"kExtAudioFileProperty_AudioConverter", err); 00150 00151 AudioConverterPrimeInfo primeInfo; 00152 UInt32 piSize=sizeof(AudioConverterPrimeInfo); 00153 memset(&primeInfo, 0, piSize); 00154 err = AudioConverterGetProperty(acRef, kAudioConverterPrimeInfo, &piSize, &primeInfo); 00155 if(err != kAudioConverterErr_PropertyNotSupported) // Only if decompressing 00156 { 00157 //_ThrowExceptionIfErr(@"kAudioConverterPrimeInfo", err); 00158 00159 m_headerFrames=primeInfo.leadingFrames; 00160 } 00161 00162 m_samples = (totalFrameCount/*-m_headerFrames*/)*m_iChannels; 00163 m_iDuration = m_samples / (inputFormat.mSampleRate * m_iChannels); 00164 m_iSampleRate = inputFormat.mSampleRate; 00165 qDebug() << m_samples << totalFrameCount << m_iChannels; 00166 00167 //Seek to position 0, which forces us to skip over all the header frames. 00168 //This makes sure we're ready to just let the Analyser rip and it'll 00169 //get the number of samples it expects (ie. no header frames). 00170 seek(0); 00171 00172 return OK; 00173 } 00174 00175 long SoundSourceCoreAudio::seek(long filepos) { 00176 // important division here, filepos is in audio samples (i.e. shorts) 00177 // but libflac expects a number in time samples. I _think_ this should 00178 // be hard-coded at two because *2 is the assumption the caller makes 00179 // -- bkgood 00180 OSStatus err = noErr; 00181 SInt64 segmentStart = filepos / 2; 00182 00183 err = ExtAudioFileSeek(m_audioFile, (SInt64)segmentStart+m_headerFrames); 00184 //_ThrowExceptionIfErr(@"ExtAudioFileSeek", err); 00185 //qDebug() << "SSCA: Seeking to" << segmentStart; 00186 00187 //err = ExtAudioFileSeek(m_audioFile, filepos / 2); 00188 if (err != noErr) 00189 { 00190 qDebug() << "SSCA: Error seeking to" << filepos;// << GetMacOSStatusErrorString(err) << GetMacOSStatusCommentString(err); 00191 } 00192 return filepos; 00193 } 00194 00195 unsigned int SoundSourceCoreAudio::read(unsigned long size, const SAMPLE *destination) { 00196 //if (!m_decoder) return 0; 00197 OSStatus err; 00198 SAMPLE *destBuffer(const_cast<SAMPLE*>(destination)); 00199 unsigned int samplesWritten = 0; 00200 unsigned int i = 0; 00201 UInt32 numFrames = 0;//(size / 2); /// m_inputFormat.mBytesPerFrame); 00202 unsigned int totalFramesToRead = size/2; 00203 unsigned int numFramesRead = 0; 00204 unsigned int numFramesToRead = totalFramesToRead; 00205 00206 while (numFramesRead < totalFramesToRead) { //FIXME: Hardcoded 2 00207 numFramesToRead = totalFramesToRead - numFramesRead; 00208 00209 AudioBufferList fillBufList; 00210 fillBufList.mNumberBuffers = 1; //Decode a single track? 00211 fillBufList.mBuffers[0].mNumberChannels = m_inputFormat.mChannelsPerFrame; 00212 fillBufList.mBuffers[0].mDataByteSize = math_min(1024, numFramesToRead*4);//numFramesToRead*sizeof(*destBuffer); // 2 = num bytes per SAMPLE 00213 fillBufList.mBuffers[0].mData = (void*)(&destBuffer[numFramesRead*2]); 00214 00215 // client format is always linear PCM - so here we determine how many frames of lpcm 00216 // we can read/write given our buffer size 00217 numFrames = numFramesToRead; //This silly variable acts as both a parameter and return value. 00218 err = ExtAudioFileRead (m_audioFile, &numFrames, &fillBufList); 00219 //The actual number of frames read also comes back in numFrames. 00220 //(It's both a parameter to a function and a return value. wat apple?) 00221 //XThrowIfError (err, "ExtAudioFileRead"); 00222 if (!numFrames) { 00223 // this is our termination condition 00224 break; 00225 } 00226 numFramesRead += numFrames; 00227 } 00228 return numFramesRead*2; 00229 } 00230 00231 inline unsigned long SoundSourceCoreAudio::length() { 00232 return m_samples; 00233 } 00234 00235 int SoundSourceCoreAudio::parseHeader() { 00236 if (getFilename().endsWith(".m4a")) 00237 setType("m4a"); 00238 else if (getFilename().endsWith(".mp3")) 00239 setType("mp3"); 00240 else if (getFilename().endsWith(".mp2")) 00241 setType("mp2"); 00242 00243 bool result = false; 00244 00245 if (getType() == "m4a") { 00246 // No need for toLocal8Bit on Windows since CoreAudio is OS X only. 00247 TagLib::MP4::File f(getFilename().toUtf8().constData()); 00248 result = processTaglibFile(f); 00249 TagLib::MP4::Tag* tag = f.tag(); 00250 if (tag) { 00251 processMP4Tag(tag); 00252 } 00253 } else if (getType() == "mp3") { 00254 // No need for toLocal8Bit on Windows since CoreAudio is OS X only. 00255 TagLib::MPEG::File f(getFilename().toUtf8().constData()); 00256 00257 // Takes care of all the default metadata 00258 result = processTaglibFile(f); 00259 00260 // Now look for MP3 specific metadata (e.g. BPM) 00261 TagLib::ID3v2::Tag* id3v2 = f.ID3v2Tag(); 00262 if (id3v2) { 00263 processID3v2Tag(id3v2); 00264 } 00265 00266 TagLib::APE::Tag *ape = f.APETag(); 00267 if (ape) { 00268 processAPETag(ape); 00269 } 00270 } else if (getType() == "mp2") { 00271 //TODO: MP2 metadata. Does anyone use mp2 files anymore? 00272 // Feels like 1995 again... 00273 } 00274 00275 00276 if (result) 00277 return OK; 00278 return ERR; 00279 } 00280 00281 00282 // static 00283 QList<QString> SoundSourceCoreAudio::supportedFileExtensions() { 00284 QList<QString> list; 00285 list.push_back("m4a"); 00286 list.push_back("mp3"); 00287 list.push_back("mp2"); 00288 //Can add mp3, mp2, ac3, and others here if you want. 00289 //See: 00290 // http://developer.apple.com/library/mac/documentation/MusicAudio/Reference/AudioFileConvertRef/Reference/reference.html#//apple_ref/doc/c_ref/AudioFileTypeID 00291 00292 //XXX: ... but make sure you implement handling for any new format in ParseHeader!!!!!! -- asantoni 00293 return list; 00294 } 00295 00296