annotate data/fileio/QuickTimeFileReader.cpp @ 392:183ee2a55fc7

* More work to abstract out interactive components used in the data library, so that it does not need to depend on QtGui.
author Chris Cannam
date Fri, 14 Mar 2008 17:14:21 +0000
parents b92513201610
children be49bf95d4a5
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@281 206 return;
Chris@281 207 }
Chris@281 208 m_d->buffer.mNumberBuffers = 1;
Chris@281 209 m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
Chris@281 210 m_d->buffer.mBuffers[0].mDataByteSize =
Chris@309 211 sizeof(float) * m_channelCount * m_d->blockSize;
Chris@309 212 m_d->data = new float[m_channelCount * m_d->blockSize];
Chris@282 213 m_d->buffer.mBuffers[0].mData = m_d->data;
Chris@281 214
Chris@281 215 initialiseDecodeCache();
Chris@281 216
Chris@281 217 if (decodeMode == DecodeAtOnce) {
Chris@281 218
Chris@392 219 if (m_reporter) {
Chris@392 220 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 221 m_reporter->setMessage
Chris@392 222 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 223 }
Chris@281 224
Chris@281 225 while (1) {
Chris@281 226
Chris@282 227 UInt32 framesRead = m_d->blockSize;
Chris@281 228 UInt32 extractionFlags = 0;
Chris@281 229 m_d->err = MovieAudioExtractionFillBuffer
Chris@281 230 (m_d->extractionSessionRef, &framesRead, &m_d->buffer,
Chris@281 231 &extractionFlags);
Chris@281 232 if (m_d->err) {
Chris@290 233 m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 234 .arg(m_d->err);
Chris@281 235 break;
Chris@281 236 }
Chris@281 237
Chris@357 238 //!!! progress?
Chris@357 239
Chris@282 240 // std::cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << std::endl;
Chris@282 241
Chris@281 242 // QuickTime buffers are interleaved unless specified otherwise
Chris@297 243 addSamplesToDecodeCache(m_d->data, framesRead);
Chris@281 244
Chris@281 245 if (framesRead < m_d->blockSize) break;
Chris@281 246 }
Chris@281 247
Chris@281 248 finishDecodeCache();
Chris@281 249
Chris@281 250 m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
Chris@281 251 if (m_d->err) {
Chris@290 252 m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
Chris@281 253 }
Chris@281 254
Chris@281 255 m_completion = 100;
Chris@281 256
Chris@392 257 } else {
Chris@392 258 if (m_reporter) m_reporter->setProgress(100);
Chris@281 259
Chris@281 260 if (m_channelCount > 0) {
Chris@281 261 m_decodeThread = new DecodeThread(this);
Chris@281 262 m_decodeThread->start();
Chris@281 263 }
Chris@281 264 }
Chris@282 265
Chris@290 266 std::cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error.toStdString() << "\"" << std::endl;
Chris@281 267 }
Chris@281 268
Chris@281 269 QuickTimeFileReader::~QuickTimeFileReader()
Chris@281 270 {
Chris@282 271 std::cerr << "QuickTimeFileReader::~QuickTimeFileReader" << std::endl;
Chris@282 272
Chris@281 273 if (m_decodeThread) {
Chris@281 274 m_cancelled = true;
Chris@281 275 m_decodeThread->wait();
Chris@281 276 delete m_decodeThread;
Chris@281 277 }
Chris@281 278
Chris@282 279 SetMovieActive(m_d->movie, FALSE);
Chris@281 280 DisposeMovie(m_d->movie);
Chris@281 281
Chris@281 282 delete[] m_d->data;
Chris@281 283 delete m_d;
Chris@281 284 }
Chris@281 285
Chris@281 286 void
Chris@392 287 QuickTimeFileReader::cancelled()
Chris@392 288 {
Chris@392 289 m_cancelled = true;
Chris@392 290 }
Chris@392 291
Chris@392 292 void
Chris@281 293 QuickTimeFileReader::DecodeThread::run()
Chris@281 294 {
Chris@297 295 if (m_reader->m_cacheMode == CacheInTemporaryFile) {
Chris@297 296 m_reader->m_completion = 1;
Chris@297 297 m_reader->startSerialised("QuickTimeFileReader::Decode");
Chris@297 298 }
Chris@297 299
Chris@281 300 while (1) {
Chris@281 301
Chris@282 302 UInt32 framesRead = m_reader->m_d->blockSize;
Chris@281 303 UInt32 extractionFlags = 0;
Chris@281 304 m_reader->m_d->err = MovieAudioExtractionFillBuffer
Chris@281 305 (m_reader->m_d->extractionSessionRef, &framesRead,
Chris@281 306 &m_reader->m_d->buffer, &extractionFlags);
Chris@281 307 if (m_reader->m_d->err) {
Chris@290 308 m_reader->m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 309 .arg(m_reader->m_d->err);
Chris@281 310 break;
Chris@281 311 }
Chris@282 312
Chris@281 313 // QuickTime buffers are interleaved unless specified otherwise
Chris@311 314 m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
Chris@281 315
Chris@281 316 if (framesRead < m_reader->m_d->blockSize) break;
Chris@281 317 }
Chris@281 318
Chris@281 319 m_reader->finishDecodeCache();
Chris@281 320
Chris@281 321 m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
Chris@281 322 if (m_reader->m_d->err) {
Chris@290 323 m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
Chris@281 324 }
Chris@281 325
Chris@281 326 m_reader->m_completion = 100;
Chris@297 327 m_reader->endSerialised();
Chris@281 328 }
Chris@281 329
Chris@281 330 void
Chris@290 331 QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@281 332 {
Chris@281 333 extensions.insert("aiff");
Chris@291 334 extensions.insert("aif");
Chris@281 335 extensions.insert("au");
Chris@281 336 extensions.insert("avi");
Chris@281 337 extensions.insert("m4a");
Chris@281 338 extensions.insert("m4b");
Chris@281 339 extensions.insert("m4p");
Chris@281 340 extensions.insert("m4v");
Chris@281 341 extensions.insert("mov");
Chris@281 342 extensions.insert("mp3");
Chris@281 343 extensions.insert("mp4");
Chris@281 344 extensions.insert("wav");
Chris@281 345 }
Chris@281 346
Chris@316 347 bool
Chris@316 348 QuickTimeFileReader::supportsExtension(QString extension)
Chris@316 349 {
Chris@316 350 std::set<QString> extensions;
Chris@316 351 getSupportedExtensions(extensions);
Chris@316 352 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 353 }
Chris@316 354
Chris@316 355 bool
Chris@316 356 QuickTimeFileReader::supportsContentType(QString type)
Chris@316 357 {
Chris@316 358 return (type == "audio/x-aiff" ||
Chris@316 359 type == "audio/x-wav" ||
Chris@316 360 type == "audio/mpeg" ||
Chris@316 361 type == "audio/basic" ||
Chris@316 362 type == "audio/x-aac" ||
Chris@316 363 type == "video/mp4" ||
Chris@316 364 type == "video/quicktime");
Chris@316 365 }
Chris@316 366
Chris@316 367 bool
Chris@317 368 QuickTimeFileReader::supports(FileSource &source)
Chris@316 369 {
Chris@316 370 return (supportsExtension(source.getExtension()) ||
Chris@316 371 supportsContentType(source.getContentType()));
Chris@316 372 }
Chris@316 373
Chris@281 374 #endif
Chris@297 375