annotate data/fileio/QuickTimeFileReader.cpp @ 1008:d9e0e59a1581

When using an aggregate model to pass data to a transform, zero-pad the shorter input to the duration of the longer rather than truncating the longer. (This is better behaviour for e.g. MATCH, and in any case the code was previously truncating incorrectly and ending up with garbage data at the end.)
author Chris Cannam
date Fri, 14 Nov 2014 13:51:33 +0000
parents d03b3d956358
children a1cd5abcb38b
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@929 46 int 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@929 53 int targetRate,
Chris@920 54 bool normalised,
Chris@392 55 ProgressReporter *reporter) :
Chris@920 56 CodedAudioFileReader(mode, targetRate, normalised),
Chris@316 57 m_source(source),
Chris@316 58 m_path(source.getLocalFilename()),
Chris@281 59 m_d(new D),
Chris@392 60 m_reporter(reporter),
Chris@281 61 m_cancelled(false),
Chris@281 62 m_completion(0),
Chris@281 63 m_decodeThread(0)
Chris@281 64 {
Chris@281 65 m_channelCount = 0;
Chris@297 66 m_fileRate = 0;
Chris@281 67
Chris@281 68 Profiler profiler("QuickTimeFileReader::QuickTimeFileReader", true);
Chris@281 69
Chris@690 70 SVDEBUG << "QuickTimeFileReader: path is \"" << m_path << "\"" << endl;
Chris@282 71
Chris@281 72 long QTversion;
Chris@281 73
Chris@281 74 #ifdef WIN32
Chris@281 75 InitializeQTML(0); // FIXME should check QT version
Chris@281 76 #else
Chris@281 77 m_d->err = Gestalt(gestaltQuickTime,&QTversion);
Chris@281 78 if ((m_d->err != noErr) || (QTversion < 0x07000000)) {
Chris@290 79 m_error = QString("Failed to find compatible version of QuickTime (version 7 or above required)");
Chris@281 80 return;
Chris@281 81 }
Chris@281 82 #endif
Chris@281 83
Chris@281 84 EnterMovies();
Chris@281 85
Chris@281 86 Handle dataRef;
Chris@281 87 OSType dataRefType;
Chris@281 88
Chris@282 89 // CFStringRef URLString = CFStringCreateWithCString
Chris@282 90 // (0, m_path.toLocal8Bit().data(), 0);
Chris@281 91
Chris@282 92
Chris@699 93 QByteArray ba = m_path.toLocal8Bit();
Chris@699 94
Chris@282 95 CFURLRef url = CFURLCreateFromFileSystemRepresentation
Chris@282 96 (kCFAllocatorDefault,
Chris@699 97 (const UInt8 *)ba.data(),
Chris@699 98 (CFIndex)ba.length(),
Chris@282 99 false);
Chris@282 100
Chris@282 101
Chris@282 102 // m_d->err = QTNewDataReferenceFromURLCFString
Chris@282 103 m_d->err = QTNewDataReferenceFromCFURL
Chris@282 104 (url, 0, &dataRef, &dataRefType);
Chris@281 105
Chris@281 106 if (m_d->err) {
Chris@290 107 m_error = QString("Error creating data reference for QuickTime decoder: code %1").arg(m_d->err);
Chris@281 108 return;
Chris@281 109 }
Chris@281 110
Chris@281 111 short fileID = movieInDataForkResID;
Chris@281 112 short flags = 0;
Chris@281 113 m_d->err = NewMovieFromDataRef
Chris@281 114 (&m_d->movie, flags, &fileID, dataRef, dataRefType);
Chris@281 115
Chris@281 116 DisposeHandle(dataRef);
Chris@281 117 if (m_d->err) {
Chris@290 118 m_error = QString("Error creating new movie for QuickTime decoder: code %1").arg(m_d->err);
Chris@281 119 return;
Chris@281 120 }
Chris@281 121
Chris@281 122 Boolean isProtected = 0;
Chris@281 123 Track aTrack = GetMovieIndTrackType
Chris@281 124 (m_d->movie, 1, SoundMediaType,
Chris@281 125 movieTrackMediaType | movieTrackEnabledOnly);
Chris@281 126
Chris@281 127 if (aTrack) {
Chris@281 128 Media aMedia = GetTrackMedia(aTrack); // get the track media
Chris@281 129 if (aMedia) {
Chris@281 130 MediaHandler mh = GetMediaHandler(aMedia); // get the media handler we can query
Chris@281 131 if (mh) {
Chris@281 132 m_d->err = QTGetComponentProperty(mh,
Chris@281 133 kQTPropertyClass_DRM,
Chris@281 134 kQTDRMPropertyID_IsProtected,
Chris@281 135 sizeof(Boolean), &isProtected,nil);
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 } else {
Chris@281 143 m_d->err = 1;
Chris@281 144 }
Chris@281 145
Chris@281 146 if (m_d->err && m_d->err != kQTPropertyNotSupportedErr) {
Chris@290 147 m_error = QString("Error checking for DRM in QuickTime decoder: code %1").arg(m_d->err);
Chris@281 148 return;
Chris@281 149 } else if (!m_d->err && isProtected) {
Chris@290 150 m_error = QString("File is protected with DRM");
Chris@281 151 return;
Chris@281 152 } else if (m_d->err == kQTPropertyNotSupportedErr && !isProtected) {
Chris@843 153 cerr << "QuickTime: File is not protected with DRM" << endl;
Chris@281 154 }
Chris@281 155
Chris@281 156 if (m_d->movie) {
Chris@281 157 SetMovieActive(m_d->movie, TRUE);
Chris@281 158 m_d->err = GetMoviesError();
Chris@281 159 if (m_d->err) {
Chris@290 160 m_error = QString("Error in QuickTime decoder activation: code %1").arg(m_d->err);
Chris@281 161 return;
Chris@281 162 }
Chris@282 163 } else {
Chris@290 164 m_error = QString("Error in QuickTime decoder: Movie object not valid");
Chris@282 165 return;
Chris@281 166 }
Chris@281 167
Chris@281 168 m_d->err = MovieAudioExtractionBegin
Chris@281 169 (m_d->movie, 0, &m_d->extractionSessionRef);
Chris@281 170 if (m_d->err) {
Chris@290 171 m_error = QString("Error in QuickTime decoder extraction init: code %1").arg(m_d->err);
Chris@281 172 return;
Chris@281 173 }
Chris@281 174
Chris@281 175 m_d->err = MovieAudioExtractionGetProperty
Chris@281 176 (m_d->extractionSessionRef,
Chris@281 177 kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
Chris@281 178 sizeof(m_d->asbd),
Chris@281 179 &m_d->asbd,
Chris@281 180 nil);
Chris@281 181
Chris@281 182 if (m_d->err) {
Chris@290 183 m_error = QString("Error in QuickTime decoder property get: code %1").arg(m_d->err);
Chris@281 184 return;
Chris@281 185 }
Chris@281 186
Chris@281 187 m_channelCount = m_d->asbd.mChannelsPerFrame;
Chris@297 188 m_fileRate = m_d->asbd.mSampleRate;
Chris@281 189
Chris@843 190 cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << endl;
Chris@281 191
Chris@281 192 m_d->asbd.mFormatFlags =
Chris@281 193 kAudioFormatFlagIsFloat |
Chris@281 194 kAudioFormatFlagIsPacked |
Chris@281 195 kAudioFormatFlagsNativeEndian;
Chris@309 196 m_d->asbd.mBitsPerChannel = sizeof(float) * 8;
Chris@309 197 m_d->asbd.mBytesPerFrame = sizeof(float) * m_d->asbd.mChannelsPerFrame;
Chris@281 198 m_d->asbd.mBytesPerPacket = m_d->asbd.mBytesPerFrame;
Chris@281 199
Chris@281 200 m_d->err = MovieAudioExtractionSetProperty
Chris@281 201 (m_d->extractionSessionRef,
Chris@281 202 kQTPropertyClass_MovieAudioExtraction_Audio,
Chris@281 203 kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
Chris@281 204 sizeof(m_d->asbd),
Chris@281 205 &m_d->asbd);
Chris@281 206
Chris@281 207 if (m_d->err) {
Chris@290 208 m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err);
Chris@441 209 m_channelCount = 0;
Chris@281 210 return;
Chris@281 211 }
Chris@281 212 m_d->buffer.mNumberBuffers = 1;
Chris@281 213 m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
Chris@281 214 m_d->buffer.mBuffers[0].mDataByteSize =
Chris@309 215 sizeof(float) * m_channelCount * m_d->blockSize;
Chris@309 216 m_d->data = new float[m_channelCount * m_d->blockSize];
Chris@282 217 m_d->buffer.mBuffers[0].mData = m_d->data;
Chris@281 218
Chris@281 219 initialiseDecodeCache();
Chris@281 220
Chris@281 221 if (decodeMode == DecodeAtOnce) {
Chris@281 222
Chris@392 223 if (m_reporter) {
Chris@392 224 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 225 m_reporter->setMessage
Chris@392 226 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 227 }
Chris@281 228
Chris@281 229 while (1) {
Chris@281 230
Chris@282 231 UInt32 framesRead = m_d->blockSize;
Chris@281 232 UInt32 extractionFlags = 0;
Chris@281 233 m_d->err = MovieAudioExtractionFillBuffer
Chris@281 234 (m_d->extractionSessionRef, &framesRead, &m_d->buffer,
Chris@281 235 &extractionFlags);
Chris@281 236 if (m_d->err) {
Chris@290 237 m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 238 .arg(m_d->err);
Chris@281 239 break;
Chris@281 240 }
Chris@281 241
Chris@357 242 //!!! progress?
Chris@357 243
Chris@843 244 // cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << endl;
Chris@282 245
Chris@281 246 // QuickTime buffers are interleaved unless specified otherwise
Chris@297 247 addSamplesToDecodeCache(m_d->data, framesRead);
Chris@281 248
Chris@281 249 if (framesRead < m_d->blockSize) break;
Chris@281 250 }
Chris@281 251
Chris@281 252 finishDecodeCache();
Chris@398 253 endSerialised();
Chris@281 254
Chris@281 255 m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef);
Chris@281 256 if (m_d->err) {
Chris@290 257 m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err);
Chris@281 258 }
Chris@281 259
Chris@281 260 m_completion = 100;
Chris@281 261
Chris@392 262 } else {
Chris@392 263 if (m_reporter) m_reporter->setProgress(100);
Chris@281 264
Chris@281 265 if (m_channelCount > 0) {
Chris@281 266 m_decodeThread = new DecodeThread(this);
Chris@281 267 m_decodeThread->start();
Chris@281 268 }
Chris@281 269 }
Chris@282 270
Chris@843 271 cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error << "\"" << endl;
Chris@281 272 }
Chris@281 273
Chris@281 274 QuickTimeFileReader::~QuickTimeFileReader()
Chris@281 275 {
Chris@690 276 SVDEBUG << "QuickTimeFileReader::~QuickTimeFileReader" << endl;
Chris@282 277
Chris@281 278 if (m_decodeThread) {
Chris@281 279 m_cancelled = true;
Chris@281 280 m_decodeThread->wait();
Chris@281 281 delete m_decodeThread;
Chris@281 282 }
Chris@281 283
Chris@282 284 SetMovieActive(m_d->movie, FALSE);
Chris@281 285 DisposeMovie(m_d->movie);
Chris@281 286
Chris@281 287 delete[] m_d->data;
Chris@281 288 delete m_d;
Chris@281 289 }
Chris@281 290
Chris@281 291 void
Chris@392 292 QuickTimeFileReader::cancelled()
Chris@392 293 {
Chris@392 294 m_cancelled = true;
Chris@392 295 }
Chris@392 296
Chris@392 297 void
Chris@281 298 QuickTimeFileReader::DecodeThread::run()
Chris@281 299 {
Chris@297 300 if (m_reader->m_cacheMode == CacheInTemporaryFile) {
Chris@297 301 m_reader->m_completion = 1;
Chris@297 302 m_reader->startSerialised("QuickTimeFileReader::Decode");
Chris@297 303 }
Chris@297 304
Chris@281 305 while (1) {
Chris@281 306
Chris@282 307 UInt32 framesRead = m_reader->m_d->blockSize;
Chris@281 308 UInt32 extractionFlags = 0;
Chris@281 309 m_reader->m_d->err = MovieAudioExtractionFillBuffer
Chris@281 310 (m_reader->m_d->extractionSessionRef, &framesRead,
Chris@281 311 &m_reader->m_d->buffer, &extractionFlags);
Chris@281 312 if (m_reader->m_d->err) {
Chris@290 313 m_reader->m_error = QString("Error in QuickTime decoding: code %1")
Chris@290 314 .arg(m_reader->m_d->err);
Chris@281 315 break;
Chris@281 316 }
Chris@282 317
Chris@281 318 // QuickTime buffers are interleaved unless specified otherwise
Chris@311 319 m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead);
Chris@281 320
Chris@281 321 if (framesRead < m_reader->m_d->blockSize) break;
Chris@281 322 }
Chris@281 323
Chris@281 324 m_reader->finishDecodeCache();
Chris@281 325
Chris@281 326 m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef);
Chris@281 327 if (m_reader->m_d->err) {
Chris@290 328 m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err);
Chris@281 329 }
Chris@281 330
Chris@281 331 m_reader->m_completion = 100;
Chris@297 332 m_reader->endSerialised();
Chris@281 333 }
Chris@281 334
Chris@281 335 void
Chris@290 336 QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@281 337 {
Chris@281 338 extensions.insert("aiff");
Chris@291 339 extensions.insert("aif");
Chris@281 340 extensions.insert("au");
Chris@281 341 extensions.insert("avi");
Chris@281 342 extensions.insert("m4a");
Chris@281 343 extensions.insert("m4b");
Chris@281 344 extensions.insert("m4p");
Chris@281 345 extensions.insert("m4v");
Chris@281 346 extensions.insert("mov");
Chris@281 347 extensions.insert("mp3");
Chris@281 348 extensions.insert("mp4");
Chris@281 349 extensions.insert("wav");
Chris@281 350 }
Chris@281 351
Chris@316 352 bool
Chris@316 353 QuickTimeFileReader::supportsExtension(QString extension)
Chris@316 354 {
Chris@316 355 std::set<QString> extensions;
Chris@316 356 getSupportedExtensions(extensions);
Chris@316 357 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 358 }
Chris@316 359
Chris@316 360 bool
Chris@316 361 QuickTimeFileReader::supportsContentType(QString type)
Chris@316 362 {
Chris@316 363 return (type == "audio/x-aiff" ||
Chris@316 364 type == "audio/x-wav" ||
Chris@316 365 type == "audio/mpeg" ||
Chris@316 366 type == "audio/basic" ||
Chris@316 367 type == "audio/x-aac" ||
Chris@316 368 type == "video/mp4" ||
Chris@316 369 type == "video/quicktime");
Chris@316 370 }
Chris@316 371
Chris@316 372 bool
Chris@317 373 QuickTimeFileReader::supports(FileSource &source)
Chris@316 374 {
Chris@316 375 return (supportsExtension(source.getExtension()) ||
Chris@316 376 supportsContentType(source.getContentType()));
Chris@316 377 }
Chris@316 378
Chris@281 379 #endif
Chris@297 380