annotate data/fileio/QuickTimeFileReader.cpp @ 1290:fa574c909c3d 3.0-integration

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