annotate data/fileio/QuickTimeFileReader.cpp @ 316:3a6725f285d6

* Make RemoteFile far more pervasive, and use it for local files as well so that we can handle both transparently. Make it shallow copy with reference counting, so it can be used by value without having to worry about the cache file lifetime. Use RemoteFile for MainWindow file-open functions, etc
author Chris Cannam
date Thu, 18 Oct 2007 15:31:20 +0000
parents 1c9143b2b658
children c324d410b096
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@281 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@316 51 QuickTimeFileReader::QuickTimeFileReader(RemoteFile 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@281 219 m_progress = new QProgressDialog
Chris@316 220 (QObject::tr("Decoding %1...").arg(QFileInfo(m_path).fileName()),
Chris@281 221 QObject::tr("Stop"), 0, 100);
Chris@281 222 m_progress->hide();
Chris@281 223
Chris@281 224 while (1) {
Chris@281 225
Chris@282 226 UInt32 framesRead = m_d->blockSize;
Chris@281 227 UInt32 extractionFlags = 0;
Chris@281 228 m_d->err = MovieAudioExtractionFillBuffer
Chris@281 229 (m_d->extractionSessionRef, &framesRead, &m_d->buffer,
Chris@281 230 &extractionFlags);
Chris@281 231 if (m_d->err) {
Chris@290 232 m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 233 .arg(m_d->err);
Chris@281 234 break;
Chris@281 235 }
Chris@281 236
Chris@282 237 // std::cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << std::endl;
Chris@282 238
Chris@281 239 // QuickTime buffers are interleaved unless specified otherwise
Chris@297 240 addSamplesToDecodeCache(m_d->data, framesRead);
Chris@281 241
Chris@281 242 if (framesRead < m_d->blockSize) break;
Chris@281 243 }
Chris@281 244
Chris@281 245 finishDecodeCache();
Chris@281 246
Chris@281 247 m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
Chris@281 248 if (m_d->err) {
Chris@290 249 m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
Chris@281 250 }
Chris@281 251
Chris@281 252 m_completion = 100;
Chris@281 253
Chris@281 254 delete m_progress;
Chris@281 255 m_progress = 0;
Chris@281 256
Chris@281 257 } else {
Chris@281 258 if (m_channelCount > 0) {
Chris@281 259 m_decodeThread = new DecodeThread(this);
Chris@281 260 m_decodeThread->start();
Chris@281 261 }
Chris@281 262 }
Chris@282 263
Chris@290 264 std::cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error.toStdString() << "\"" << std::endl;
Chris@281 265 }
Chris@281 266
Chris@281 267 QuickTimeFileReader::~QuickTimeFileReader()
Chris@281 268 {
Chris@282 269 std::cerr << "QuickTimeFileReader::~QuickTimeFileReader" << std::endl;
Chris@282 270
Chris@281 271 if (m_decodeThread) {
Chris@281 272 m_cancelled = true;
Chris@281 273 m_decodeThread->wait();
Chris@281 274 delete m_decodeThread;
Chris@281 275 }
Chris@281 276
Chris@282 277 SetMovieActive(m_d->movie, FALSE);
Chris@281 278 DisposeMovie(m_d->movie);
Chris@281 279
Chris@281 280 delete[] m_d->data;
Chris@281 281 delete m_d;
Chris@281 282 }
Chris@281 283
Chris@281 284 void
Chris@281 285 QuickTimeFileReader::DecodeThread::run()
Chris@281 286 {
Chris@297 287 if (m_reader->m_cacheMode == CacheInTemporaryFile) {
Chris@297 288 m_reader->m_completion = 1;
Chris@297 289 m_reader->startSerialised("QuickTimeFileReader::Decode");
Chris@297 290 }
Chris@297 291
Chris@281 292 while (1) {
Chris@281 293
Chris@282 294 UInt32 framesRead = m_reader->m_d->blockSize;
Chris@281 295 UInt32 extractionFlags = 0;
Chris@281 296 m_reader->m_d->err = MovieAudioExtractionFillBuffer
Chris@281 297 (m_reader->m_d->extractionSessionRef, &framesRead,
Chris@281 298 &m_reader->m_d->buffer, &extractionFlags);
Chris@281 299 if (m_reader->m_d->err) {
Chris@290 300 m_reader->m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 301 .arg(m_reader->m_d->err);
Chris@281 302 break;
Chris@281 303 }
Chris@282 304
Chris@281 305 // QuickTime buffers are interleaved unless specified otherwise
Chris@311 306 m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
Chris@281 307
Chris@281 308 if (framesRead < m_reader->m_d->blockSize) break;
Chris@281 309 }
Chris@281 310
Chris@281 311 m_reader->finishDecodeCache();
Chris@281 312
Chris@281 313 m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
Chris@281 314 if (m_reader->m_d->err) {
Chris@290 315 m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
Chris@281 316 }
Chris@281 317
Chris@281 318 m_reader->m_completion = 100;
Chris@297 319 m_reader->endSerialised();
Chris@281 320 }
Chris@281 321
Chris@281 322 void
Chris@290 323 QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@281 324 {
Chris@281 325 extensions.insert("aiff");
Chris@291 326 extensions.insert("aif");
Chris@281 327 extensions.insert("au");
Chris@281 328 extensions.insert("avi");
Chris@281 329 extensions.insert("m4a");
Chris@281 330 extensions.insert("m4b");
Chris@281 331 extensions.insert("m4p");
Chris@281 332 extensions.insert("m4v");
Chris@281 333 extensions.insert("mov");
Chris@281 334 extensions.insert("mp3");
Chris@281 335 extensions.insert("mp4");
Chris@281 336 extensions.insert("wav");
Chris@281 337 }
Chris@281 338
Chris@316 339 bool
Chris@316 340 QuickTimeFileReader::supportsExtension(QString extension)
Chris@316 341 {
Chris@316 342 std::set<QString> extensions;
Chris@316 343 getSupportedExtensions(extensions);
Chris@316 344 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 345 }
Chris@316 346
Chris@316 347 bool
Chris@316 348 QuickTimeFileReader::supportsContentType(QString type)
Chris@316 349 {
Chris@316 350 return (type == "audio/x-aiff" ||
Chris@316 351 type == "audio/x-wav" ||
Chris@316 352 type == "audio/mpeg" ||
Chris@316 353 type == "audio/basic" ||
Chris@316 354 type == "audio/x-aac" ||
Chris@316 355 type == "video/mp4" ||
Chris@316 356 type == "video/quicktime");
Chris@316 357 }
Chris@316 358
Chris@316 359 bool
Chris@316 360 QuickTimeFileReader::supports(RemoteFile &source)
Chris@316 361 {
Chris@316 362 return (supportsExtension(source.getExtension()) ||
Chris@316 363 supportsContentType(source.getContentType()));
Chris@316 364 }
Chris@316 365
Chris@281 366 #endif
Chris@297 367