Chris@281: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@281: Chris@281: /* Chris@281: Sonic Visualiser Chris@281: An audio file viewer and annotation editor. Chris@281: Centre for Digital Music, Queen Mary, University of London. Chris@281: This file copyright 2006-2007 Chris Cannam and QMUL. Chris@281: Chris@281: Based on QTAudioFile.cpp from SoundBite, copyright 2006 Chris@281: Chris Sutton and Mark Levy. Chris@281: Chris@281: This program is free software; you can redistribute it and/or Chris@281: modify it under the terms of the GNU General Public License as Chris@281: published by the Free Software Foundation; either version 2 of the Chris@281: License, or (at your option) any later version. See the file Chris@281: COPYING included with this distribution for more information. Chris@281: */ Chris@281: Chris@281: #ifdef HAVE_QUICKTIME Chris@281: Chris@281: #include "QuickTimeFileReader.h" Chris@281: #include "base/Profiler.h" Chris@392: #include "base/ProgressReporter.h" Chris@281: #include "system/System.h" Chris@281: Chris@281: #include Chris@281: Chris@333: #ifdef _WIN32 Chris@281: #include Chris@281: #include Chris@281: #else Chris@281: #include Chris@281: #endif Chris@281: Chris@281: class QuickTimeFileReader::D Chris@281: { Chris@281: public: Chris@281: D() : data(0), blockSize(1024) { } Chris@281: Chris@281: MovieAudioExtractionRef extractionSessionRef; Chris@281: AudioBufferList buffer; Chris@309: float *data; Chris@281: OSErr err; Chris@281: AudioStreamBasicDescription asbd; Chris@281: Movie movie; Chris@929: int blockSize; Chris@281: }; Chris@281: Chris@281: Chris@317: QuickTimeFileReader::QuickTimeFileReader(FileSource source, Chris@281: DecodeMode decodeMode, Chris@297: CacheMode mode, Chris@1040: sv_samplerate_t targetRate, Chris@920: bool normalised, Chris@392: ProgressReporter *reporter) : Chris@920: CodedAudioFileReader(mode, targetRate, normalised), Chris@316: m_source(source), Chris@316: m_path(source.getLocalFilename()), Chris@281: m_d(new D), Chris@392: m_reporter(reporter), Chris@281: m_cancelled(false), Chris@281: m_completion(0), Chris@281: m_decodeThread(0) Chris@281: { Chris@1279: SVDEBUG << "QuickTimeFileReader: local path: \"" << m_path Chris@1279: << "\", decode mode: " << decodeMode << " (" Chris@1279: << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded") Chris@1279: << ")" << endl; Chris@1279: Chris@281: m_channelCount = 0; Chris@297: m_fileRate = 0; Chris@281: Chris@281: Profiler profiler("QuickTimeFileReader::QuickTimeFileReader", true); Chris@281: Chris@281: long QTversion; Chris@281: Chris@281: #ifdef WIN32 Chris@281: InitializeQTML(0); // FIXME should check QT version Chris@281: #else Chris@281: m_d->err = Gestalt(gestaltQuickTime,&QTversion); Chris@281: if ((m_d->err != noErr) || (QTversion < 0x07000000)) { Chris@290: m_error = QString("Failed to find compatible version of QuickTime (version 7 or above required)"); Chris@281: return; Chris@281: } Chris@281: #endif Chris@281: Chris@281: EnterMovies(); Chris@281: Chris@281: Handle dataRef; Chris@281: OSType dataRefType; Chris@281: Chris@282: // CFStringRef URLString = CFStringCreateWithCString Chris@282: // (0, m_path.toLocal8Bit().data(), 0); Chris@281: Chris@282: Chris@699: QByteArray ba = m_path.toLocal8Bit(); Chris@699: Chris@282: CFURLRef url = CFURLCreateFromFileSystemRepresentation Chris@282: (kCFAllocatorDefault, Chris@699: (const UInt8 *)ba.data(), Chris@699: (CFIndex)ba.length(), Chris@282: false); Chris@282: Chris@282: Chris@282: // m_d->err = QTNewDataReferenceFromURLCFString Chris@282: m_d->err = QTNewDataReferenceFromCFURL Chris@282: (url, 0, &dataRef, &dataRefType); Chris@281: Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error creating data reference for QuickTime decoder: code %1").arg(m_d->err); Chris@281: return; Chris@281: } Chris@281: Chris@281: short fileID = movieInDataForkResID; Chris@281: short flags = 0; Chris@281: m_d->err = NewMovieFromDataRef Chris@281: (&m_d->movie, flags, &fileID, dataRef, dataRefType); Chris@281: Chris@281: DisposeHandle(dataRef); Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error creating new movie for QuickTime decoder: code %1").arg(m_d->err); Chris@281: return; Chris@281: } Chris@281: Chris@281: Boolean isProtected = 0; Chris@281: Track aTrack = GetMovieIndTrackType Chris@281: (m_d->movie, 1, SoundMediaType, Chris@281: movieTrackMediaType | movieTrackEnabledOnly); Chris@281: Chris@281: if (aTrack) { Chris@281: Media aMedia = GetTrackMedia(aTrack); // get the track media Chris@281: if (aMedia) { Chris@281: MediaHandler mh = GetMediaHandler(aMedia); // get the media handler we can query Chris@281: if (mh) { Chris@281: m_d->err = QTGetComponentProperty(mh, Chris@281: kQTPropertyClass_DRM, Chris@281: kQTDRMPropertyID_IsProtected, Chris@281: sizeof(Boolean), &isProtected,nil); Chris@281: } else { Chris@281: m_d->err = 1; Chris@281: } Chris@281: } else { Chris@281: m_d->err = 1; Chris@281: } Chris@281: } else { Chris@281: m_d->err = 1; Chris@281: } Chris@281: Chris@281: if (m_d->err && m_d->err != kQTPropertyNotSupportedErr) { Chris@290: m_error = QString("Error checking for DRM in QuickTime decoder: code %1").arg(m_d->err); Chris@281: return; Chris@281: } else if (!m_d->err && isProtected) { Chris@290: m_error = QString("File is protected with DRM"); Chris@281: return; Chris@281: } else if (m_d->err == kQTPropertyNotSupportedErr && !isProtected) { Chris@843: cerr << "QuickTime: File is not protected with DRM" << endl; Chris@281: } Chris@281: Chris@281: if (m_d->movie) { Chris@281: SetMovieActive(m_d->movie, TRUE); Chris@281: m_d->err = GetMoviesError(); Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error in QuickTime decoder activation: code %1").arg(m_d->err); Chris@281: return; Chris@281: } Chris@282: } else { Chris@290: m_error = QString("Error in QuickTime decoder: Movie object not valid"); Chris@282: return; Chris@281: } Chris@281: Chris@281: m_d->err = MovieAudioExtractionBegin Chris@281: (m_d->movie, 0, &m_d->extractionSessionRef); Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error in QuickTime decoder extraction init: code %1").arg(m_d->err); Chris@281: return; Chris@281: } Chris@281: Chris@281: m_d->err = MovieAudioExtractionGetProperty Chris@281: (m_d->extractionSessionRef, Chris@281: kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, Chris@281: sizeof(m_d->asbd), Chris@281: &m_d->asbd, Chris@281: nil); Chris@281: Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error in QuickTime decoder property get: code %1").arg(m_d->err); Chris@281: return; Chris@281: } Chris@281: Chris@281: m_channelCount = m_d->asbd.mChannelsPerFrame; Chris@297: m_fileRate = m_d->asbd.mSampleRate; Chris@281: Chris@843: cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << endl; Chris@281: Chris@281: m_d->asbd.mFormatFlags = Chris@281: kAudioFormatFlagIsFloat | Chris@281: kAudioFormatFlagIsPacked | Chris@281: kAudioFormatFlagsNativeEndian; Chris@309: m_d->asbd.mBitsPerChannel = sizeof(float) * 8; Chris@309: m_d->asbd.mBytesPerFrame = sizeof(float) * m_d->asbd.mChannelsPerFrame; Chris@281: m_d->asbd.mBytesPerPacket = m_d->asbd.mBytesPerFrame; Chris@281: Chris@281: m_d->err = MovieAudioExtractionSetProperty Chris@281: (m_d->extractionSessionRef, Chris@281: kQTPropertyClass_MovieAudioExtraction_Audio, Chris@281: kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, Chris@281: sizeof(m_d->asbd), Chris@281: &m_d->asbd); Chris@281: Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err); Chris@441: m_channelCount = 0; Chris@281: return; Chris@281: } Chris@281: m_d->buffer.mNumberBuffers = 1; Chris@281: m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount; Chris@281: m_d->buffer.mBuffers[0].mDataByteSize = Chris@309: sizeof(float) * m_channelCount * m_d->blockSize; Chris@309: m_d->data = new float[m_channelCount * m_d->blockSize]; Chris@282: m_d->buffer.mBuffers[0].mData = m_d->data; Chris@281: Chris@281: initialiseDecodeCache(); Chris@281: Chris@281: if (decodeMode == DecodeAtOnce) { Chris@281: Chris@392: if (m_reporter) { Chris@392: connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled())); Chris@392: m_reporter->setMessage Chris@392: (tr("Decoding %1...").arg(QFileInfo(m_path).fileName())); Chris@327: } Chris@281: Chris@281: while (1) { Chris@281: Chris@282: UInt32 framesRead = m_d->blockSize; Chris@281: UInt32 extractionFlags = 0; Chris@281: m_d->err = MovieAudioExtractionFillBuffer Chris@281: (m_d->extractionSessionRef, &framesRead, &m_d->buffer, Chris@281: &extractionFlags); Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error in QuickTime decoding: code %1") Chris@290: .arg(m_d->err); Chris@281: break; Chris@281: } Chris@281: Chris@357: //!!! progress? Chris@357: Chris@843: // cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << endl; Chris@282: Chris@281: // QuickTime buffers are interleaved unless specified otherwise Chris@297: addSamplesToDecodeCache(m_d->data, framesRead); Chris@281: Chris@281: if (framesRead < m_d->blockSize) break; Chris@281: } Chris@281: Chris@281: finishDecodeCache(); Chris@398: endSerialised(); Chris@281: Chris@281: m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef); Chris@281: if (m_d->err) { Chris@290: m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err); Chris@281: } Chris@281: Chris@281: m_completion = 100; Chris@281: Chris@392: } else { Chris@392: if (m_reporter) m_reporter->setProgress(100); Chris@281: Chris@281: if (m_channelCount > 0) { Chris@281: m_decodeThread = new DecodeThread(this); Chris@281: m_decodeThread->start(); Chris@281: } Chris@281: } Chris@282: Chris@843: cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error << "\"" << endl; Chris@281: } Chris@281: Chris@281: QuickTimeFileReader::~QuickTimeFileReader() Chris@281: { Chris@690: SVDEBUG << "QuickTimeFileReader::~QuickTimeFileReader" << endl; Chris@282: Chris@281: if (m_decodeThread) { Chris@281: m_cancelled = true; Chris@281: m_decodeThread->wait(); Chris@281: delete m_decodeThread; Chris@281: } Chris@281: Chris@282: SetMovieActive(m_d->movie, FALSE); Chris@281: DisposeMovie(m_d->movie); Chris@281: Chris@281: delete[] m_d->data; Chris@281: delete m_d; Chris@281: } Chris@281: Chris@281: void Chris@392: QuickTimeFileReader::cancelled() Chris@392: { Chris@392: m_cancelled = true; Chris@392: } Chris@392: Chris@392: void Chris@281: QuickTimeFileReader::DecodeThread::run() Chris@281: { Chris@297: if (m_reader->m_cacheMode == CacheInTemporaryFile) { Chris@297: m_reader->m_completion = 1; Chris@297: m_reader->startSerialised("QuickTimeFileReader::Decode"); Chris@297: } Chris@297: Chris@281: while (1) { Chris@281: Chris@282: UInt32 framesRead = m_reader->m_d->blockSize; Chris@281: UInt32 extractionFlags = 0; Chris@281: m_reader->m_d->err = MovieAudioExtractionFillBuffer Chris@281: (m_reader->m_d->extractionSessionRef, &framesRead, Chris@281: &m_reader->m_d->buffer, &extractionFlags); Chris@281: if (m_reader->m_d->err) { Chris@290: m_reader->m_error = QString("Error in QuickTime decoding: code %1") Chris@290: .arg(m_reader->m_d->err); Chris@281: break; Chris@281: } Chris@282: Chris@281: // QuickTime buffers are interleaved unless specified otherwise Chris@311: m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead); Chris@281: Chris@281: if (framesRead < m_reader->m_d->blockSize) break; Chris@281: } Chris@281: Chris@281: m_reader->finishDecodeCache(); Chris@281: Chris@281: m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef); Chris@281: if (m_reader->m_d->err) { Chris@290: m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err); Chris@281: } Chris@281: Chris@281: m_reader->m_completion = 100; Chris@297: m_reader->endSerialised(); Chris@281: } Chris@281: Chris@281: void Chris@290: QuickTimeFileReader::getSupportedExtensions(std::set &extensions) Chris@281: { Chris@281: extensions.insert("aiff"); Chris@291: extensions.insert("aif"); Chris@281: extensions.insert("au"); Chris@281: extensions.insert("avi"); Chris@281: extensions.insert("m4a"); Chris@281: extensions.insert("m4b"); Chris@281: extensions.insert("m4p"); Chris@281: extensions.insert("m4v"); Chris@281: extensions.insert("mov"); Chris@281: extensions.insert("mp3"); Chris@281: extensions.insert("mp4"); Chris@281: extensions.insert("wav"); Chris@281: } Chris@281: Chris@316: bool Chris@316: QuickTimeFileReader::supportsExtension(QString extension) Chris@316: { Chris@316: std::set extensions; Chris@316: getSupportedExtensions(extensions); Chris@316: return (extensions.find(extension.toLower()) != extensions.end()); Chris@316: } Chris@316: Chris@316: bool Chris@316: QuickTimeFileReader::supportsContentType(QString type) Chris@316: { Chris@316: return (type == "audio/x-aiff" || Chris@316: type == "audio/x-wav" || Chris@316: type == "audio/mpeg" || Chris@316: type == "audio/basic" || Chris@316: type == "audio/x-aac" || Chris@316: type == "video/mp4" || Chris@316: type == "video/quicktime"); Chris@316: } Chris@316: Chris@316: bool Chris@317: QuickTimeFileReader::supports(FileSource &source) Chris@316: { Chris@316: return (supportsExtension(source.getExtension()) || Chris@316: supportsContentType(source.getContentType())); Chris@316: } Chris@316: Chris@281: #endif Chris@297: