annotate data/fileio/QuickTimeFileReader.cpp @ 490:c3fb8258e34d

* Make it possible to import an entire session from an RDF document. However, at the moment the timings of events appear to be constrained by how far the audio decoder has got through its audio file at the time the event is queried -- need to investigate.
author Chris Cannam
date Fri, 21 Nov 2008 18:03:14 +0000
parents 288f45533041
children b4a8d8221eaf
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@316 69 std::cerr << "QuickTimeFileReader: path is \"" << m_path.toStdString() << "\"" << std::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@282 92 CFURLRef url = CFURLCreateFromFileSystemRepresentation
Chris@282 93 (kCFAllocatorDefault,
Chris@316 94 (const UInt8 *)m_path.toLocal8Bit().data(),
Chris@316 95 (CFIndex)m_path.length(),
Chris@282 96 false);
Chris@282 97
Chris@282 98
Chris@282 99 // m_d->err = QTNewDataReferenceFromURLCFString
Chris@282 100 m_d->err = QTNewDataReferenceFromCFURL
Chris@282 101 (url, 0, &dataRef, &dataRefType);
Chris@281 102
Chris@281 103 if (m_d->err) {
Chris@290 104 m_error = QString("Error creating data reference for QuickTime decoder: code %1").arg(m_d->err);
Chris@281 105 return;
Chris@281 106 }
Chris@281 107
Chris@281 108 short fileID = movieInDataForkResID;
Chris@281 109 short flags = 0;
Chris@281 110 m_d->err = NewMovieFromDataRef
Chris@281 111 (&m_d->movie, flags, &fileID, dataRef, dataRefType);
Chris@281 112
Chris@281 113 DisposeHandle(dataRef);
Chris@281 114 if (m_d->err) {
Chris@290 115 m_error = QString("Error creating new movie for QuickTime decoder: code %1").arg(m_d->err);
Chris@281 116 return;
Chris@281 117 }
Chris@281 118
Chris@281 119 Boolean isProtected = 0;
Chris@281 120 Track aTrack = GetMovieIndTrackType
Chris@281 121 (m_d->movie, 1, SoundMediaType,
Chris@281 122 movieTrackMediaType | movieTrackEnabledOnly);
Chris@281 123
Chris@281 124 if (aTrack) {
Chris@281 125 Media aMedia = GetTrackMedia(aTrack); // get the track media
Chris@281 126 if (aMedia) {
Chris@281 127 MediaHandler mh = GetMediaHandler(aMedia); // get the media handler we can query
Chris@281 128 if (mh) {
Chris@281 129 m_d->err = QTGetComponentProperty(mh,
Chris@281 130 kQTPropertyClass_DRM,
Chris@281 131 kQTDRMPropertyID_IsProtected,
Chris@281 132 sizeof(Boolean), &isProtected,nil);
Chris@281 133 } else {
Chris@281 134 m_d->err = 1;
Chris@281 135 }
Chris@281 136 } else {
Chris@281 137 m_d->err = 1;
Chris@281 138 }
Chris@281 139 } else {
Chris@281 140 m_d->err = 1;
Chris@281 141 }
Chris@281 142
Chris@281 143 if (m_d->err && m_d->err != kQTPropertyNotSupportedErr) {
Chris@290 144 m_error = QString("Error checking for DRM in QuickTime decoder: code %1").arg(m_d->err);
Chris@281 145 return;
Chris@281 146 } else if (!m_d->err && isProtected) {
Chris@290 147 m_error = QString("File is protected with DRM");
Chris@281 148 return;
Chris@281 149 } else if (m_d->err == kQTPropertyNotSupportedErr && !isProtected) {
Chris@281 150 std::cerr << "QuickTime: File is not protected with DRM" << std::endl;
Chris@281 151 }
Chris@281 152
Chris@281 153 if (m_d->movie) {
Chris@281 154 SetMovieActive(m_d->movie, TRUE);
Chris@281 155 m_d->err = GetMoviesError();
Chris@281 156 if (m_d->err) {
Chris@290 157 m_error = QString("Error in QuickTime decoder activation: code %1").arg(m_d->err);
Chris@281 158 return;
Chris@281 159 }
Chris@282 160 } else {
Chris@290 161 m_error = QString("Error in QuickTime decoder: Movie object not valid");
Chris@282 162 return;
Chris@281 163 }
Chris@281 164
Chris@281 165 m_d->err = MovieAudioExtractionBegin
Chris@281 166 (m_d->movie, 0, &m_d->extractionSessionRef);
Chris@281 167 if (m_d->err) {
Chris@290 168 m_error = QString("Error in QuickTime decoder extraction init: code %1").arg(m_d->err);
Chris@281 169 return;
Chris@281 170 }
Chris@281 171
Chris@281 172 m_d->err = MovieAudioExtractionGetProperty
Chris@281 173 (m_d->extractionSessionRef,
Chris@281 174 kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
Chris@281 175 sizeof(m_d->asbd),
Chris@281 176 &m_d->asbd,
Chris@281 177 nil);
Chris@281 178
Chris@281 179 if (m_d->err) {
Chris@290 180 m_error = QString("Error in QuickTime decoder property get: code %1").arg(m_d->err);
Chris@281 181 return;
Chris@281 182 }
Chris@281 183
Chris@281 184 m_channelCount = m_d->asbd.mChannelsPerFrame;
Chris@297 185 m_fileRate = m_d->asbd.mSampleRate;
Chris@281 186
Chris@297 187 std::cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << std::endl;
Chris@281 188
Chris@281 189 m_d->asbd.mFormatFlags =
Chris@281 190 kAudioFormatFlagIsFloat |
Chris@281 191 kAudioFormatFlagIsPacked |
Chris@281 192 kAudioFormatFlagsNativeEndian;
Chris@309 193 m_d->asbd.mBitsPerChannel = sizeof(float) * 8;
Chris@309 194 m_d->asbd.mBytesPerFrame = sizeof(float) * m_d->asbd.mChannelsPerFrame;
Chris@281 195 m_d->asbd.mBytesPerPacket = m_d->asbd.mBytesPerFrame;
Chris@281 196
Chris@281 197 m_d->err = MovieAudioExtractionSetProperty
Chris@281 198 (m_d->extractionSessionRef,
Chris@281 199 kQTPropertyClass_MovieAudioExtraction_Audio,
Chris@281 200 kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
Chris@281 201 sizeof(m_d->asbd),
Chris@281 202 &m_d->asbd);
Chris@281 203
Chris@281 204 if (m_d->err) {
Chris@290 205 m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err);
Chris@441 206 m_channelCount = 0;
Chris@281 207 return;
Chris@281 208 }
Chris@281 209 m_d->buffer.mNumberBuffers = 1;
Chris@281 210 m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
Chris@281 211 m_d->buffer.mBuffers[0].mDataByteSize =
Chris@309 212 sizeof(float) * m_channelCount * m_d->blockSize;
Chris@309 213 m_d->data = new float[m_channelCount * m_d->blockSize];
Chris@282 214 m_d->buffer.mBuffers[0].mData = m_d->data;
Chris@281 215
Chris@281 216 initialiseDecodeCache();
Chris@281 217
Chris@281 218 if (decodeMode == DecodeAtOnce) {
Chris@281 219
Chris@392 220 if (m_reporter) {
Chris@392 221 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 222 m_reporter->setMessage
Chris@392 223 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 224 }
Chris@281 225
Chris@281 226 while (1) {
Chris@281 227
Chris@282 228 UInt32 framesRead = m_d->blockSize;
Chris@281 229 UInt32 extractionFlags = 0;
Chris@281 230 m_d->err = MovieAudioExtractionFillBuffer
Chris@281 231 (m_d->extractionSessionRef, &framesRead, &m_d->buffer,
Chris@281 232 &extractionFlags);
Chris@281 233 if (m_d->err) {
Chris@290 234 m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 235 .arg(m_d->err);
Chris@281 236 break;
Chris@281 237 }
Chris@281 238
Chris@357 239 //!!! progress?
Chris@357 240
Chris@282 241 // std::cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << std::endl;
Chris@282 242
Chris@281 243 // QuickTime buffers are interleaved unless specified otherwise
Chris@297 244 addSamplesToDecodeCache(m_d->data, framesRead);
Chris@281 245
Chris@281 246 if (framesRead < m_d->blockSize) break;
Chris@281 247 }
Chris@281 248
Chris@281 249 finishDecodeCache();
Chris@398 250 endSerialised();
Chris@281 251
Chris@281 252 m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
Chris@281 253 if (m_d->err) {
Chris@290 254 m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
Chris@281 255 }
Chris@281 256
Chris@281 257 m_completion = 100;
Chris@281 258
Chris@392 259 } else {
Chris@392 260 if (m_reporter) m_reporter->setProgress(100);
Chris@281 261
Chris@281 262 if (m_channelCount > 0) {
Chris@281 263 m_decodeThread = new DecodeThread(this);
Chris@281 264 m_decodeThread->start();
Chris@281 265 }
Chris@281 266 }
Chris@282 267
Chris@290 268 std::cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error.toStdString() << "\"" << std::endl;
Chris@281 269 }
Chris@281 270
Chris@281 271 QuickTimeFileReader::~QuickTimeFileReader()
Chris@281 272 {
Chris@282 273 std::cerr << "QuickTimeFileReader::~QuickTimeFileReader" << std::endl;
Chris@282 274
Chris@281 275 if (m_decodeThread) {
Chris@281 276 m_cancelled = true;
Chris@281 277 m_decodeThread->wait();
Chris@281 278 delete m_decodeThread;
Chris@281 279 }
Chris@281 280
Chris@282 281 SetMovieActive(m_d->movie, FALSE);
Chris@281 282 DisposeMovie(m_d->movie);
Chris@281 283
Chris@281 284 delete[] m_d->data;
Chris@281 285 delete m_d;
Chris@281 286 }
Chris@281 287
Chris@281 288 void
Chris@392 289 QuickTimeFileReader::cancelled()
Chris@392 290 {
Chris@392 291 m_cancelled = true;
Chris@392 292 }
Chris@392 293
Chris@392 294 void
Chris@281 295 QuickTimeFileReader::DecodeThread::run()
Chris@281 296 {
Chris@297 297 if (m_reader->m_cacheMode == CacheInTemporaryFile) {
Chris@297 298 m_reader->m_completion = 1;
Chris@297 299 m_reader->startSerialised("QuickTimeFileReader::Decode");
Chris@297 300 }
Chris@297 301
Chris@281 302 while (1) {
Chris@281 303
Chris@282 304 UInt32 framesRead = m_reader->m_d->blockSize;
Chris@281 305 UInt32 extractionFlags = 0;
Chris@281 306 m_reader->m_d->err = MovieAudioExtractionFillBuffer
Chris@281 307 (m_reader->m_d->extractionSessionRef, &framesRead,
Chris@281 308 &m_reader->m_d->buffer, &extractionFlags);
Chris@281 309 if (m_reader->m_d->err) {
Chris@290 310 m_reader->m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 311 .arg(m_reader->m_d->err);
Chris@281 312 break;
Chris@281 313 }
Chris@282 314
Chris@281 315 // QuickTime buffers are interleaved unless specified otherwise
Chris@311 316 m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
Chris@281 317
Chris@281 318 if (framesRead < m_reader->m_d->blockSize) break;
Chris@281 319 }
Chris@281 320
Chris@281 321 m_reader->finishDecodeCache();
Chris@281 322
Chris@281 323 m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
Chris@281 324 if (m_reader->m_d->err) {
Chris@290 325 m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
Chris@281 326 }
Chris@281 327
Chris@281 328 m_reader->m_completion = 100;
Chris@297 329 m_reader->endSerialised();
Chris@281 330 }
Chris@281 331
Chris@281 332 void
Chris@290 333 QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@281 334 {
Chris@281 335 extensions.insert("aiff");
Chris@291 336 extensions.insert("aif");
Chris@281 337 extensions.insert("au");
Chris@281 338 extensions.insert("avi");
Chris@281 339 extensions.insert("m4a");
Chris@281 340 extensions.insert("m4b");
Chris@281 341 extensions.insert("m4p");
Chris@281 342 extensions.insert("m4v");
Chris@281 343 extensions.insert("mov");
Chris@281 344 extensions.insert("mp3");
Chris@281 345 extensions.insert("mp4");
Chris@281 346 extensions.insert("wav");
Chris@281 347 }
Chris@281 348
Chris@316 349 bool
Chris@316 350 QuickTimeFileReader::supportsExtension(QString extension)
Chris@316 351 {
Chris@316 352 std::set<QString> extensions;
Chris@316 353 getSupportedExtensions(extensions);
Chris@316 354 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 355 }
Chris@316 356
Chris@316 357 bool
Chris@316 358 QuickTimeFileReader::supportsContentType(QString type)
Chris@316 359 {
Chris@316 360 return (type == "audio/x-aiff" ||
Chris@316 361 type == "audio/x-wav" ||
Chris@316 362 type == "audio/mpeg" ||
Chris@316 363 type == "audio/basic" ||
Chris@316 364 type == "audio/x-aac" ||
Chris@316 365 type == "video/mp4" ||
Chris@316 366 type == "video/quicktime");
Chris@316 367 }
Chris@316 368
Chris@316 369 bool
Chris@317 370 QuickTimeFileReader::supports(FileSource &source)
Chris@316 371 {
Chris@316 372 return (supportsExtension(source.getExtension()) ||
Chris@316 373 supportsContentType(source.getContentType()));
Chris@316 374 }
Chris@316 375
Chris@281 376 #endif
Chris@297 377