annotate data/fileio/QuickTimeFileReader.cpp @ 360:ac300d385ab2

* Various fixes to object lifetime management, particularly in the spectrum layer and for notification of main model deletion. The main purpose of this is to improve the behaviour of the spectrum, but I think it may also help with #1840922 Various crashes in Layer Summary window.
author Chris Cannam
date Wed, 23 Jan 2008 15:43:27 +0000
parents b92513201610
children 183ee2a55fc7
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@281 23 #include "system/System.h"
Chris@281 24
Chris@281 25 #include <QApplication>
Chris@281 26 #include <QFileInfo>
Chris@281 27 #include <QProgressDialog>
Chris@281 28
Chris@333 29 #ifdef _WIN32
Chris@281 30 #include <QTML.h>
Chris@281 31 #include <Movies.h>
Chris@281 32 #else
Chris@281 33 #include <QuickTime/QuickTime.h>
Chris@281 34 #endif
Chris@281 35
Chris@281 36 class QuickTimeFileReader::D
Chris@281 37 {
Chris@281 38 public:
Chris@281 39 D() : data(0), blockSize(1024) { }
Chris@281 40
Chris@281 41 MovieAudioExtractionRef extractionSessionRef;
Chris@281 42 AudioBufferList buffer;
Chris@309 43 float *data;
Chris@281 44 OSErr err;
Chris@281 45 AudioStreamBasicDescription asbd;
Chris@281 46 Movie movie;
Chris@281 47 size_t blockSize;
Chris@281 48 };
Chris@281 49
Chris@281 50
Chris@317 51 QuickTimeFileReader::QuickTimeFileReader(FileSource source,
Chris@281 52 DecodeMode decodeMode,
Chris@297 53 CacheMode mode,
Chris@297 54 size_t targetRate) :
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@281 59 m_progress(0),
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@327 219 if (dynamic_cast<QApplication *>(QCoreApplication::instance())) {
Chris@327 220 m_progress = new QProgressDialog
Chris@327 221 (QObject::tr("Decoding %1...").arg(QFileInfo(m_path).fileName()),
Chris@327 222 QObject::tr("Stop"), 0, 100);
Chris@327 223 m_progress->hide();
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@281 250
Chris@281 251 m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
Chris@281 252 if (m_d->err) {
Chris@290 253 m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
Chris@281 254 }
Chris@281 255
Chris@281 256 m_completion = 100;
Chris@281 257
Chris@281 258 delete m_progress;
Chris@281 259 m_progress = 0;
Chris@281 260
Chris@281 261 } else {
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@281 289 QuickTimeFileReader::DecodeThread::run()
Chris@281 290 {
Chris@297 291 if (m_reader->m_cacheMode == CacheInTemporaryFile) {
Chris@297 292 m_reader->m_completion = 1;
Chris@297 293 m_reader->startSerialised("QuickTimeFileReader::Decode");
Chris@297 294 }
Chris@297 295
Chris@281 296 while (1) {
Chris@281 297
Chris@282 298 UInt32 framesRead = m_reader->m_d->blockSize;
Chris@281 299 UInt32 extractionFlags = 0;
Chris@281 300 m_reader->m_d->err = MovieAudioExtractionFillBuffer
Chris@281 301 (m_reader->m_d->extractionSessionRef, &framesRead,
Chris@281 302 &m_reader->m_d->buffer, &extractionFlags);
Chris@281 303 if (m_reader->m_d->err) {
Chris@290 304 m_reader->m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 305 .arg(m_reader->m_d->err);
Chris@281 306 break;
Chris@281 307 }
Chris@282 308
Chris@281 309 // QuickTime buffers are interleaved unless specified otherwise
Chris@311 310 m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
Chris@281 311
Chris@281 312 if (framesRead < m_reader->m_d->blockSize) break;
Chris@281 313 }
Chris@281 314
Chris@281 315 m_reader->finishDecodeCache();
Chris@281 316
Chris@281 317 m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
Chris@281 318 if (m_reader->m_d->err) {
Chris@290 319 m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
Chris@281 320 }
Chris@281 321
Chris@281 322 m_reader->m_completion = 100;
Chris@297 323 m_reader->endSerialised();
Chris@281 324 }
Chris@281 325
Chris@281 326 void
Chris@290 327 QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@281 328 {
Chris@281 329 extensions.insert("aiff");
Chris@291 330 extensions.insert("aif");
Chris@281 331 extensions.insert("au");
Chris@281 332 extensions.insert("avi");
Chris@281 333 extensions.insert("m4a");
Chris@281 334 extensions.insert("m4b");
Chris@281 335 extensions.insert("m4p");
Chris@281 336 extensions.insert("m4v");
Chris@281 337 extensions.insert("mov");
Chris@281 338 extensions.insert("mp3");
Chris@281 339 extensions.insert("mp4");
Chris@281 340 extensions.insert("wav");
Chris@281 341 }
Chris@281 342
Chris@316 343 bool
Chris@316 344 QuickTimeFileReader::supportsExtension(QString extension)
Chris@316 345 {
Chris@316 346 std::set<QString> extensions;
Chris@316 347 getSupportedExtensions(extensions);
Chris@316 348 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 349 }
Chris@316 350
Chris@316 351 bool
Chris@316 352 QuickTimeFileReader::supportsContentType(QString type)
Chris@316 353 {
Chris@316 354 return (type == "audio/x-aiff" ||
Chris@316 355 type == "audio/x-wav" ||
Chris@316 356 type == "audio/mpeg" ||
Chris@316 357 type == "audio/basic" ||
Chris@316 358 type == "audio/x-aac" ||
Chris@316 359 type == "video/mp4" ||
Chris@316 360 type == "video/quicktime");
Chris@316 361 }
Chris@316 362
Chris@316 363 bool
Chris@317 364 QuickTimeFileReader::supports(FileSource &source)
Chris@316 365 {
Chris@316 366 return (supportsExtension(source.getExtension()) ||
Chris@316 367 supportsContentType(source.getContentType()));
Chris@316 368 }
Chris@316 369
Chris@281 370 #endif
Chris@297 371