annotate data/fileio/QuickTimeFileReader.cpp @ 823:f0558e69a074

Rename Resampling- to DecodingWavFileReader, and use it whenever we have an audio file that is not quickly seekable using libsndfile. Avoids very slow performance when analysing ogg files.
author Chris Cannam
date Wed, 17 Jul 2013 15:40:01 +0100
parents 3b00c1650490
children e802e550a1f2
rev   line source
Chris@281 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@281 2
Chris@281 3 /*
Chris@281 4 Sonic Visualiser
Chris@281 5 An audio file viewer and annotation editor.
Chris@281 6 Centre for Digital Music, Queen Mary, University of London.
Chris@281 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@281 8
Chris@281 9 Based on QTAudioFile.cpp from SoundBite, copyright 2006
Chris@281 10 Chris Sutton and Mark Levy.
Chris@281 11
Chris@281 12 This program is free software; you can redistribute it and/or
Chris@281 13 modify it under the terms of the GNU General Public License as
Chris@281 14 published by the Free Software Foundation; either version 2 of the
Chris@281 15 License, or (at your option) any later version. See the file
Chris@281 16 COPYING included with this distribution for more information.
Chris@281 17 */
Chris@281 18
Chris@281 19 #ifdef HAVE_QUICKTIME
Chris@281 20
Chris@281 21 #include "QuickTimeFileReader.h"
Chris@281 22 #include "base/Profiler.h"
Chris@392 23 #include "base/ProgressReporter.h"
Chris@281 24 #include "system/System.h"
Chris@281 25
Chris@281 26 #include <QFileInfo>
Chris@281 27
Chris@333 28 #ifdef _WIN32
Chris@281 29 #include <QTML.h>
Chris@281 30 #include <Movies.h>
Chris@281 31 #else
Chris@281 32 #include <QuickTime/QuickTime.h>
Chris@281 33 #endif
Chris@281 34
Chris@281 35 class QuickTimeFileReader::D
Chris@281 36 {
Chris@281 37 public:
Chris@281 38 D() : data(0), blockSize(1024) { }
Chris@281 39
Chris@281 40 MovieAudioExtractionRef extractionSessionRef;
Chris@281 41 AudioBufferList buffer;
Chris@309 42 float *data;
Chris@281 43 OSErr err;
Chris@281 44 AudioStreamBasicDescription asbd;
Chris@281 45 Movie movie;
Chris@281 46 size_t blockSize;
Chris@281 47 };
Chris@281 48
Chris@281 49
Chris@317 50 QuickTimeFileReader::QuickTimeFileReader(FileSource source,
Chris@281 51 DecodeMode decodeMode,
Chris@297 52 CacheMode mode,
Chris@392 53 size_t targetRate,
Chris@392 54 ProgressReporter *reporter) :
Chris@297 55 CodedAudioFileReader(mode, targetRate),
Chris@316 56 m_source(source),
Chris@316 57 m_path(source.getLocalFilename()),
Chris@281 58 m_d(new D),
Chris@392 59 m_reporter(reporter),
Chris@281 60 m_cancelled(false),
Chris@281 61 m_completion(0),
Chris@281 62 m_decodeThread(0)
Chris@281 63 {
Chris@281 64 m_channelCount = 0;
Chris@297 65 m_fileRate = 0;
Chris@281 66
Chris@281 67 Profiler profiler("QuickTimeFileReader::QuickTimeFileReader", true);
Chris@281 68
Chris@690 69 SVDEBUG << "QuickTimeFileReader: path is \"" << m_path << "\"" << endl;
Chris@282 70
Chris@281 71 long QTversion;
Chris@281 72
Chris@281 73 #ifdef WIN32
Chris@281 74 InitializeQTML(0); // FIXME should check QT version
Chris@281 75 #else
Chris@281 76 m_d->err = Gestalt(gestaltQuickTime,&QTversion);
Chris@281 77 if ((m_d->err != noErr) || (QTversion < 0x07000000)) {
Chris@290 78 m_error = QString("Failed to find compatible version of QuickTime (version 7 or above required)");
Chris@281 79 return;
Chris@281 80 }
Chris@281 81 #endif
Chris@281 82
Chris@281 83 EnterMovies();
Chris@281 84
Chris@281 85 Handle dataRef;
Chris@281 86 OSType dataRefType;
Chris@281 87
Chris@282 88 // CFStringRef URLString = CFStringCreateWithCString
Chris@282 89 // (0, m_path.toLocal8Bit().data(), 0);
Chris@281 90
Chris@282 91
Chris@699 92 QByteArray ba = m_path.toLocal8Bit();
Chris@699 93
Chris@282 94 CFURLRef url = CFURLCreateFromFileSystemRepresentation
Chris@282 95 (kCFAllocatorDefault,
Chris@699 96 (const UInt8 *)ba.data(),
Chris@699 97 (CFIndex)ba.length(),
Chris@282 98 false);
Chris@282 99
Chris@282 100
Chris@282 101 // m_d->err = QTNewDataReferenceFromURLCFString
Chris@282 102 m_d->err = QTNewDataReferenceFromCFURL
Chris@282 103 (url, 0, &dataRef, &dataRefType);
Chris@281 104
Chris@281 105 if (m_d->err) {
Chris@290 106 m_error = QString("Error creating data reference for QuickTime decoder: code %1").arg(m_d->err);
Chris@281 107 return;
Chris@281 108 }
Chris@281 109
Chris@281 110 short fileID = movieInDataForkResID;
Chris@281 111 short flags = 0;
Chris@281 112 m_d->err = NewMovieFromDataRef
Chris@281 113 (&m_d->movie, flags, &fileID, dataRef, dataRefType);
Chris@281 114
Chris@281 115 DisposeHandle(dataRef);
Chris@281 116 if (m_d->err) {
Chris@290 117 m_error = QString("Error creating new movie for QuickTime decoder: code %1").arg(m_d->err);
Chris@281 118 return;
Chris@281 119 }
Chris@281 120
Chris@281 121 Boolean isProtected = 0;
Chris@281 122 Track aTrack = GetMovieIndTrackType
Chris@281 123 (m_d->movie, 1, SoundMediaType,
Chris@281 124 movieTrackMediaType | movieTrackEnabledOnly);
Chris@281 125
Chris@281 126 if (aTrack) {
Chris@281 127 Media aMedia = GetTrackMedia(aTrack); // get the track media
Chris@281 128 if (aMedia) {
Chris@281 129 MediaHandler mh = GetMediaHandler(aMedia); // get the media handler we can query
Chris@281 130 if (mh) {
Chris@281 131 m_d->err = QTGetComponentProperty(mh,
Chris@281 132 kQTPropertyClass_DRM,
Chris@281 133 kQTDRMPropertyID_IsProtected,
Chris@281 134 sizeof(Boolean), &isProtected,nil);
Chris@281 135 } else {
Chris@281 136 m_d->err = 1;
Chris@281 137 }
Chris@281 138 } else {
Chris@281 139 m_d->err = 1;
Chris@281 140 }
Chris@281 141 } else {
Chris@281 142 m_d->err = 1;
Chris@281 143 }
Chris@281 144
Chris@281 145 if (m_d->err && m_d->err != kQTPropertyNotSupportedErr) {
Chris@290 146 m_error = QString("Error checking for DRM in QuickTime decoder: code %1").arg(m_d->err);
Chris@281 147 return;
Chris@281 148 } else if (!m_d->err && isProtected) {
Chris@290 149 m_error = QString("File is protected with DRM");
Chris@281 150 return;
Chris@281 151 } else if (m_d->err == kQTPropertyNotSupportedErr && !isProtected) {
Chris@281 152 std::cerr << "QuickTime: File is not protected with DRM" << std::endl;
Chris@281 153 }
Chris@281 154
Chris@281 155 if (m_d->movie) {
Chris@281 156 SetMovieActive(m_d->movie, TRUE);
Chris@281 157 m_d->err = GetMoviesError();
Chris@281 158 if (m_d->err) {
Chris@290 159 m_error = QString("Error in QuickTime decoder activation: code %1").arg(m_d->err);
Chris@281 160 return;
Chris@281 161 }
Chris@282 162 } else {
Chris@290 163 m_error = QString("Error in QuickTime decoder: Movie object not valid");
Chris@282 164 return;
Chris@281 165 }
Chris@281 166
Chris@281 167 m_d->err = MovieAudioExtractionBegin
Chris@281 168 (m_d->movie, 0, &m_d->extractionSessionRef);
Chris@281 169 if (m_d->err) {
Chris@290 170 m_error = QString("Error in QuickTime decoder extraction init: code %1").arg(m_d->err);
Chris@281 171 return;
Chris@281 172 }
Chris@281 173
Chris@281 174 m_d->err = MovieAudioExtractionGetProperty
Chris@281 175 (m_d->extractionSessionRef,
Chris@281 176 kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
Chris@281 177 sizeof(m_d->asbd),
Chris@281 178 &m_d->asbd,
Chris@281 179 nil);
Chris@281 180
Chris@281 181 if (m_d->err) {
Chris@290 182 m_error = QString("Error in QuickTime decoder property get: code %1").arg(m_d->err);
Chris@281 183 return;
Chris@281 184 }
Chris@281 185
Chris@281 186 m_channelCount = m_d->asbd.mChannelsPerFrame;
Chris@297 187 m_fileRate = m_d->asbd.mSampleRate;
Chris@281 188
Chris@297 189 std::cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << std::endl;
Chris@281 190
Chris@281 191 m_d->asbd.mFormatFlags =
Chris@281 192 kAudioFormatFlagIsFloat |
Chris@281 193 kAudioFormatFlagIsPacked |
Chris@281 194 kAudioFormatFlagsNativeEndian;
Chris@309 195 m_d->asbd.mBitsPerChannel = sizeof(float) * 8;
Chris@309 196 m_d->asbd.mBytesPerFrame = sizeof(float) * m_d->asbd.mChannelsPerFrame;
Chris@281 197 m_d->asbd.mBytesPerPacket = m_d->asbd.mBytesPerFrame;
Chris@281 198
Chris@281 199 m_d->err = MovieAudioExtractionSetProperty
Chris@281 200 (m_d->extractionSessionRef,
Chris@281 201 kQTPropertyClass_MovieAudioExtraction_Audio,
Chris@281 202 kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
Chris@281 203 sizeof(m_d->asbd),
Chris@281 204 &m_d->asbd);
Chris@281 205
Chris@281 206 if (m_d->err) {
Chris@290 207 m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err);
Chris@441 208 m_channelCount = 0;
Chris@281 209 return;
Chris@281 210 }
Chris@281 211 m_d->buffer.mNumberBuffers = 1;
Chris@281 212 m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
Chris@281 213 m_d->buffer.mBuffers[0].mDataByteSize =
Chris@309 214 sizeof(float) * m_channelCount * m_d->blockSize;
Chris@309 215 m_d->data = new float[m_channelCount * m_d->blockSize];
Chris@282 216 m_d->buffer.mBuffers[0].mData = m_d->data;
Chris@281 217
Chris@281 218 initialiseDecodeCache();
Chris@281 219
Chris@281 220 if (decodeMode == DecodeAtOnce) {
Chris@281 221
Chris@392 222 if (m_reporter) {
Chris@392 223 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 224 m_reporter->setMessage
Chris@392 225 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 226 }
Chris@281 227
Chris@281 228 while (1) {
Chris@281 229
Chris@282 230 UInt32 framesRead = m_d->blockSize;
Chris@281 231 UInt32 extractionFlags = 0;
Chris@281 232 m_d->err = MovieAudioExtractionFillBuffer
Chris@281 233 (m_d->extractionSessionRef, &framesRead, &m_d->buffer,
Chris@281 234 &extractionFlags);
Chris@281 235 if (m_d->err) {
Chris@290 236 m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 237 .arg(m_d->err);
Chris@281 238 break;
Chris@281 239 }
Chris@281 240
Chris@357 241 //!!! progress?
Chris@357 242
Chris@282 243 // std::cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << std::endl;
Chris@282 244
Chris@281 245 // QuickTime buffers are interleaved unless specified otherwise
Chris@297 246 addSamplesToDecodeCache(m_d->data, framesRead);
Chris@281 247
Chris@281 248 if (framesRead < m_d->blockSize) break;
Chris@281 249 }
Chris@281 250
Chris@281 251 finishDecodeCache();
Chris@398 252 endSerialised();
Chris@281 253
Chris@281 254 m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
Chris@281 255 if (m_d->err) {
Chris@290 256 m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
Chris@281 257 }
Chris@281 258
Chris@281 259 m_completion = 100;
Chris@281 260
Chris@392 261 } else {
Chris@392 262 if (m_reporter) m_reporter->setProgress(100);
Chris@281 263
Chris@281 264 if (m_channelCount > 0) {
Chris@281 265 m_decodeThread = new DecodeThread(this);
Chris@281 266 m_decodeThread->start();
Chris@281 267 }
Chris@281 268 }
Chris@282 269
Chris@686 270 std::cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error << "\"" << std::endl;
Chris@281 271 }
Chris@281 272
Chris@281 273 QuickTimeFileReader::~QuickTimeFileReader()
Chris@281 274 {
Chris@690 275 SVDEBUG << "QuickTimeFileReader::~QuickTimeFileReader" << endl;
Chris@282 276
Chris@281 277 if (m_decodeThread) {
Chris@281 278 m_cancelled = true;
Chris@281 279 m_decodeThread->wait();
Chris@281 280 delete m_decodeThread;
Chris@281 281 }
Chris@281 282
Chris@282 283 SetMovieActive(m_d->movie, FALSE);
Chris@281 284 DisposeMovie(m_d->movie);
Chris@281 285
Chris@281 286 delete[] m_d->data;
Chris@281 287 delete m_d;
Chris@281 288 }
Chris@281 289
Chris@281 290 void
Chris@392 291 QuickTimeFileReader::cancelled()
Chris@392 292 {
Chris@392 293 m_cancelled = true;
Chris@392 294 }
Chris@392 295
Chris@392 296 void
Chris@281 297 QuickTimeFileReader::DecodeThread::run()
Chris@281 298 {
Chris@297 299 if (m_reader->m_cacheMode == CacheInTemporaryFile) {
Chris@297 300 m_reader->m_completion = 1;
Chris@297 301 m_reader->startSerialised("QuickTimeFileReader::Decode");
Chris@297 302 }
Chris@297 303
Chris@281 304 while (1) {
Chris@281 305
Chris@282 306 UInt32 framesRead = m_reader->m_d->blockSize;
Chris@281 307 UInt32 extractionFlags = 0;
Chris@281 308 m_reader->m_d->err = MovieAudioExtractionFillBuffer
Chris@281 309 (m_reader->m_d->extractionSessionRef, &framesRead,
Chris@281 310 &m_reader->m_d->buffer, &extractionFlags);
Chris@281 311 if (m_reader->m_d->err) {
Chris@290 312 m_reader->m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 313 .arg(m_reader->m_d->err);
Chris@281 314 break;
Chris@281 315 }
Chris@282 316
Chris@281 317 // QuickTime buffers are interleaved unless specified otherwise
Chris@311 318 m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
Chris@281 319
Chris@281 320 if (framesRead < m_reader->m_d->blockSize) break;
Chris@281 321 }
Chris@281 322
Chris@281 323 m_reader->finishDecodeCache();
Chris@281 324
Chris@281 325 m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
Chris@281 326 if (m_reader->m_d->err) {
Chris@290 327 m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
Chris@281 328 }
Chris@281 329
Chris@281 330 m_reader->m_completion = 100;
Chris@297 331 m_reader->endSerialised();
Chris@281 332 }
Chris@281 333
Chris@281 334 void
Chris@290 335 QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@281 336 {
Chris@281 337 extensions.insert("aiff");
Chris@291 338 extensions.insert("aif");
Chris@281 339 extensions.insert("au");
Chris@281 340 extensions.insert("avi");
Chris@281 341 extensions.insert("m4a");
Chris@281 342 extensions.insert("m4b");
Chris@281 343 extensions.insert("m4p");
Chris@281 344 extensions.insert("m4v");
Chris@281 345 extensions.insert("mov");
Chris@281 346 extensions.insert("mp3");
Chris@281 347 extensions.insert("mp4");
Chris@281 348 extensions.insert("wav");
Chris@281 349 }
Chris@281 350
Chris@316 351 bool
Chris@316 352 QuickTimeFileReader::supportsExtension(QString extension)
Chris@316 353 {
Chris@316 354 std::set<QString> extensions;
Chris@316 355 getSupportedExtensions(extensions);
Chris@316 356 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 357 }
Chris@316 358
Chris@316 359 bool
Chris@316 360 QuickTimeFileReader::supportsContentType(QString type)
Chris@316 361 {
Chris@316 362 return (type == "audio/x-aiff" ||
Chris@316 363 type == "audio/x-wav" ||
Chris@316 364 type == "audio/mpeg" ||
Chris@316 365 type == "audio/basic" ||
Chris@316 366 type == "audio/x-aac" ||
Chris@316 367 type == "video/mp4" ||
Chris@316 368 type == "video/quicktime");
Chris@316 369 }
Chris@316 370
Chris@316 371 bool
Chris@317 372 QuickTimeFileReader::supports(FileSource &source)
Chris@316 373 {
Chris@316 374 return (supportsExtension(source.getExtension()) ||
Chris@316 375 supportsContentType(source.getContentType()));
Chris@316 376 }
Chris@316 377
Chris@281 378 #endif
Chris@297 379