changeset 263:71dfc6ab3b54

* Threaded mp3/ogg file reading. Not activated yet, as it doesn't work in context (SV needs to know the duration of its main model at the outset)
author Chris Cannam
date Thu, 24 May 2007 16:20:22 +0000
parents 524bcd89743b
children 260032c26c4f
files data/fileio/AudioFileReaderFactory.cpp data/fileio/CodedAudioFileReader.cpp data/fileio/CodedAudioFileReader.h data/fileio/MP3FileReader.cpp data/fileio/MP3FileReader.h data/fileio/OggVorbisFileReader.cpp data/fileio/OggVorbisFileReader.h system/Init.cpp
diffstat 8 files changed, 183 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/data/fileio/AudioFileReaderFactory.cpp	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/AudioFileReaderFactory.cpp	Thu May 24 16:20:22 2007 +0000
@@ -73,7 +73,9 @@
         MP3FileReader::getSupportedExtensions(extensions);
         if (extensions.find(ext) != extensions.end()) {
             reader = new MP3FileReader
-                (path, true, MP3FileReader::CacheInTemporaryFile);
+                (path,
+                 MP3FileReader::DecodeAtOnce,
+                 MP3FileReader::CacheInTemporaryFile);
         }
     }
 #endif
@@ -84,7 +86,9 @@
         OggVorbisFileReader::getSupportedExtensions(extensions);
         if (extensions.find(ext) != extensions.end()) {
             reader = new OggVorbisFileReader
-                (path, true, OggVorbisFileReader::CacheInTemporaryFile);
+                (path, 
+                 OggVorbisFileReader::DecodeAtOnce,
+                 OggVorbisFileReader::CacheInTemporaryFile);
         }
     }
 #endif
@@ -119,7 +123,9 @@
 #ifdef HAVE_OGGZ
 #ifdef HAVE_FISHSOUND
     reader = new OggVorbisFileReader
-        (path, true, OggVorbisFileReader::CacheInTemporaryFile);
+        (path,
+         OggVorbisFileReader::DecodeAtOnce,
+         OggVorbisFileReader::CacheInTemporaryFile);
     if (reader->isOK()) return reader;
     if (reader->getError() != "") {
 	std::cerr << "AudioFileReaderFactory: Ogg file reader error: \""
@@ -134,7 +140,9 @@
  
 #ifdef HAVE_MAD
     reader = new MP3FileReader
-        (path, true, MP3FileReader::CacheInTemporaryFile);
+        (path,
+         MP3FileReader::DecodeAtOnce,
+         MP3FileReader::CacheInTemporaryFile);
     if (reader->isOK()) return reader;
     if (reader->getError() != "") {
 	std::cerr << "AudioFileReaderFactory: MP3 file reader error: \""
--- a/data/fileio/CodedAudioFileReader.cpp	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/CodedAudioFileReader.cpp	Thu May 24 16:20:22 2007 +0000
@@ -22,6 +22,7 @@
 
 #include <iostream>
 #include <QDir>
+#include <QMutexLocker>
 
 CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode) :
     m_cacheMode(cacheMode),
@@ -36,6 +37,8 @@
 
 CodedAudioFileReader::~CodedAudioFileReader()
 {
+    QMutexLocker locker(&m_cacheMutex);
+
     if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
     if (m_cacheFileReader) delete m_cacheFileReader;
     if (m_cacheWriteBuffer) delete[] m_cacheWriteBuffer;
@@ -50,6 +53,8 @@
 void
 CodedAudioFileReader::initialiseDecodeCache()
 {
+    QMutexLocker locker(&m_cacheMutex);
+
     if (m_cacheMode == CacheInTemporaryFile) {
 
         m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
@@ -88,6 +93,8 @@
 void
 CodedAudioFileReader::addSampleToDecodeCache(float sample)
 {
+    QMutexLocker locker(&m_cacheMutex);
+
     if (!m_initialised) return;
 
     switch (m_cacheMode) {
@@ -117,6 +124,8 @@
 void
 CodedAudioFileReader::finishDecodeCache()
 {
+    QMutexLocker locker(&m_cacheMutex);
+
     Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
 
     if (!m_initialised) {
@@ -164,6 +173,9 @@
 CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count,
                                            SampleBlock &frames) const
 {
+    //!!! we want to ensure this doesn't require a lock -- at the
+    // moment it does need one, but it doesn't have one...
+
     if (!m_initialised) return;
 
     switch (m_cacheMode) {
--- a/data/fileio/CodedAudioFileReader.h	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/CodedAudioFileReader.h	Thu May 24 16:20:22 2007 +0000
@@ -19,6 +19,7 @@
 #include "AudioFileReader.h"
 
 #include <sndfile.h>
+#include <QMutex>
 
 class WavFileReader;
 
@@ -43,6 +44,7 @@
     void finishDecodeCache();
     bool isDecodeCacheInitialised() const { return m_initialised; }
 
+    QMutex m_cacheMutex;
     CacheMode m_cacheMode;
     SampleBlock m_data;
     bool m_initialised;
--- a/data/fileio/MP3FileReader.cpp	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/MP3FileReader.cpp	Thu May 24 16:20:22 2007 +0000
@@ -29,7 +29,7 @@
 #include <QFileInfo>
 #include <QProgressDialog>
 
-MP3FileReader::MP3FileReader(QString path, bool showProgress, CacheMode mode) :
+MP3FileReader::MP3FileReader(QString path, DecodeMode decodeMode, CacheMode mode) :
     CodedAudioFileReader(mode),
     m_path(path)
 {
@@ -41,6 +41,8 @@
     m_bitrateDenom = 0;
     m_frameCount = 0;
     m_cancelled = false;
+    m_done = false;
+    m_progress = 0;
 
     struct stat stat;
     if (::stat(path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) {
@@ -60,10 +62,10 @@
 	return;
     }	
 
-    unsigned char *filebuffer = 0;
+    m_filebuffer = 0;
 
     try {
-        filebuffer = new unsigned char[m_fileSize];
+        m_filebuffer = new unsigned char[m_fileSize];
     } catch (...) {
         m_error = QString("Out of memory");
         ::close(fd);
@@ -73,11 +75,11 @@
     ssize_t sz = 0;
     size_t offset = 0;
     while (offset < m_fileSize) {
-        sz = ::read(fd, filebuffer + offset, m_fileSize - offset);
+        sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset);
         if (sz < 0) {
             m_error = QString("Read error for file %1 (after %2 bytes)")
                 .arg(path).arg(offset);
-            delete[] filebuffer;
+            delete[] m_filebuffer;
             ::close(fd);
             return;
         } else if (sz == 0) {
@@ -91,33 +93,59 @@
 
     ::close(fd);
 
-    if (showProgress) {
+    if (decodeMode == DecodeAtOnce) {
+
 	m_progress = new QProgressDialog
 	    (QObject::tr("Decoding %1...").arg(QFileInfo(path).fileName()),
 	     QObject::tr("Stop"), 0, 100);
 	m_progress->hide();
-    }
 
-    if (!decode(filebuffer, m_fileSize)) {
-	m_error = QString("Failed to decode file %1.").arg(path);
-        delete[] filebuffer;
-	return;
-    }
-    
-    if (isDecodeCacheInitialised()) finishDecodeCache();
+        if (!decode(m_filebuffer, m_fileSize)) {
+            m_error = QString("Failed to decode file %1.").arg(path);
+        }
+        
+        delete[] m_filebuffer;
+        m_filebuffer = 0;
 
-    if (showProgress) {
+        if (isDecodeCacheInitialised()) finishDecodeCache();
+
 	delete m_progress;
 	m_progress = 0;
+
+    } else {
+
+        m_decodeThread = new DecodeThread(this);
+        m_decodeThread->start();
+
+        while (m_channelCount == 0 && !m_done) {
+            usleep(10);
+        }
     }
-
-    delete[] filebuffer;
 }
 
 MP3FileReader::~MP3FileReader()
 {
+    if (m_decodeThread) {
+        m_decodeThread->wait();
+        delete m_decodeThread;
+    }
 }
 
+void
+MP3FileReader::DecodeThread::run()
+{
+    if (!m_reader->decode(m_reader->m_filebuffer, m_reader->m_fileSize)) {
+        m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path);
+    }
+
+    delete[] m_reader->m_filebuffer;
+    m_reader->m_filebuffer = 0;
+    
+    if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
+
+    m_reader->m_done = true;
+} 
+
 bool
 MP3FileReader::decode(void *mm, size_t sz)
 {
@@ -132,6 +160,7 @@
     mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
     mad_decoder_finish(&decoder);
 
+    m_done = true;
     return true;
 }
 
@@ -180,16 +209,18 @@
         double duration = double(m_fileSize * 8) / bitrate;
         double elapsed = double(m_frameCount) / m_sampleRate;
         double percent = ((elapsed * 100.0) / duration);
-        int progress = int(percent);
-        if (progress < 1) progress = 1;
-        if (progress > 99) progress = 99;
-        if (progress > m_progress->value()) {
-            m_progress->setValue(progress);
-            m_progress->show();
-            m_progress->raise();
-            qApp->processEvents();
-            if (m_progress->wasCanceled()) {
-                m_cancelled = true;
+        if (m_progress) {
+            int progress = int(percent);
+            if (progress < 1) progress = 1;
+            if (progress > 99) progress = 99;
+            if (progress > m_progress->value()) {
+                m_progress->setValue(progress);
+                m_progress->show();
+                m_progress->raise();
+                qApp->processEvents();
+                if (m_progress->wasCanceled()) {
+                    m_cancelled = true;
+                }
             }
         }
     }
--- a/data/fileio/MP3FileReader.h	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/MP3FileReader.h	Thu May 24 16:20:22 2007 +0000
@@ -20,6 +20,7 @@
 
 #include "CodedAudioFileReader.h"
 
+#include "base/Thread.h"
 #include <mad.h>
 
 #include <set>
@@ -29,19 +30,31 @@
 class MP3FileReader : public CodedAudioFileReader
 {
 public:
-    MP3FileReader(QString path, bool showProgress, CacheMode cacheMode);
+    enum DecodeMode {
+        DecodeAtOnce, // decode the file on construction, with progress dialog
+        DecodeThreaded // decode in a background thread after construction
+    };
+
+    MP3FileReader(QString path, DecodeMode decodeMode, CacheMode cacheMode);
     virtual ~MP3FileReader();
 
     virtual QString getError() const { return m_error; }
 
     static void getSupportedExtensions(std::set<QString> &extensions);
     
+    virtual bool isUpdating() const {
+        return m_decodeThread && m_decodeThread->isRunning();
+    }
+
 protected:
     QString m_path;
     QString m_error;
     size_t m_fileSize;
     double m_bitrateNum;
     size_t m_bitrateDenom;
+    bool m_done;
+
+    unsigned char *m_filebuffer;
 
     QProgressDialog *m_progress;
     bool m_cancelled;
@@ -59,6 +72,18 @@
     static enum mad_flow input(void *, struct mad_stream *);
     static enum mad_flow output(void *, struct mad_header const *, struct mad_pcm *);
     static enum mad_flow error(void *, struct mad_stream *, struct mad_frame *);
+
+    class DecodeThread : public Thread
+    {
+    public:
+        DecodeThread(MP3FileReader *reader) : m_reader(reader) { }
+        virtual void run();
+
+    protected:
+        MP3FileReader *m_reader;
+    };
+
+    DecodeThread *m_decodeThread;
 };
 
 #endif
--- a/data/fileio/OggVorbisFileReader.cpp	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/OggVorbisFileReader.cpp	Thu May 24 16:20:22 2007 +0000
@@ -32,14 +32,16 @@
 
 static int instances = 0;
 
-OggVorbisFileReader::OggVorbisFileReader(QString path, bool showProgress,
+OggVorbisFileReader::OggVorbisFileReader(QString path,
+                                         DecodeMode decodeMode,
                                          CacheMode mode) :
     CodedAudioFileReader(mode),
     m_path(path),
     m_progress(0),
     m_fileSize(0),
     m_bytesRead(0),
-    m_cancelled(false)
+    m_cancelled(false),
+    m_decodeThread(0)
 {
     m_frameCount = 0;
     m_channelCount = 0;
@@ -52,8 +54,7 @@
     QFileInfo info(path);
     m_fileSize = info.size();
 
-    OGGZ *oggz;
-    if (!(oggz = oggz_open(path.toLocal8Bit().data(), OGGZ_READ))) {
+    if (!(m_oggz = oggz_open(path.toLocal8Bit().data(), OGGZ_READ))) {
 	m_error = QString("File %1 is not an OGG file.").arg(path);
 	return;
     }
@@ -62,34 +63,63 @@
     m_fishSound = fish_sound_new(FISH_SOUND_DECODE, &fsinfo);
 
     fish_sound_set_decoded_callback(m_fishSound, acceptFrames, this);
-    oggz_set_read_callback(oggz, -1, readPacket, this);
+    oggz_set_read_callback(m_oggz, -1, readPacket, this);
 
-    if (showProgress) {
+    if (decodeMode == DecodeAtOnce) {
+
 	m_progress = new QProgressDialog
 	    (QObject::tr("Decoding %1...").arg(QFileInfo(path).fileName()),
 	     QObject::tr("Stop"), 0, 100);
 	m_progress->hide();
-    }
 
-    while (oggz_read(oggz, 1024) > 0);
+        while (oggz_read(m_oggz, 1024) > 0);
+        
+        fish_sound_delete(m_fishSound);
+        m_fishSound = 0;
+        oggz_close(m_oggz);
+        m_oggz = 0;
 
-    fish_sound_delete(m_fishSound);
-    m_fishSound = 0;
-    oggz_close(oggz);
+        if (isDecodeCacheInitialised()) finishDecodeCache();
 
-    if (isDecodeCacheInitialised()) finishDecodeCache();
+        if (decodeMode == DecodeAtOnce) {
+            delete m_progress;
+            m_progress = 0;
+        }
 
-    if (showProgress) {
-	delete m_progress;
-	m_progress = 0;
+    } else {
+
+        while (oggz_read(m_oggz, 1024) > 0 &&
+               m_channelCount == 0);
+
+        if (m_channelCount > 0) {
+            m_decodeThread = new DecodeThread(this);
+            m_decodeThread->start();
+        }
     }
 }
 
 OggVorbisFileReader::~OggVorbisFileReader()
 {
     std::cerr << "OggVorbisFileReader::~OggVorbisFileReader(" << m_path.toLocal8Bit().data() << "): now have " << (--instances) << " instances" << std::endl;
+    if (m_decodeThread) {
+        m_decodeThread->wait();
+        delete m_decodeThread;
+    }
 }
 
+void
+OggVorbisFileReader::DecodeThread::run()
+{
+    while (oggz_read(m_reader->m_oggz, 1024) > 0);
+        
+    fish_sound_delete(m_reader->m_fishSound);
+    m_reader->m_fishSound = 0;
+    oggz_close(m_reader->m_oggz);
+    m_reader->m_oggz = 0;
+    
+    if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
+} 
+
 int
 OggVorbisFileReader::readPacket(OGGZ *, ogg_packet *packet, long, void *data)
 {
@@ -144,7 +174,6 @@
 	for (long i = 0; i < nframes; ++i) {
 	    for (size_t c = 0; c < reader->m_channelCount; ++c) {
                 reader->addSampleToDecodeCache(frames[c][i]);
-//		reader->m_data.push_back(frames[c][i]);
 	    }
 	}
 
--- a/data/fileio/OggVorbisFileReader.h	Thu May 10 12:48:26 2007 +0000
+++ b/data/fileio/OggVorbisFileReader.h	Thu May 24 16:20:22 2007 +0000
@@ -21,6 +21,7 @@
 
 #include "CodedAudioFileReader.h"
 
+#include "base/Thread.h"
 #include <oggz/oggz.h>
 #include <fishsound/fishsound.h>
 
@@ -31,17 +32,28 @@
 class OggVorbisFileReader : public CodedAudioFileReader
 {
 public:
-    OggVorbisFileReader(QString path, bool showProgress, CacheMode cacheMode);
+    enum DecodeMode {
+        DecodeAtOnce, // decode the file on construction, with progress dialog
+        DecodeThreaded // decode in a background thread after construction
+    };
+
+    OggVorbisFileReader(QString path, DecodeMode decodeMode,
+                        CacheMode cacheMode);
     virtual ~OggVorbisFileReader();
 
     virtual QString getError() const { return m_error; }
 
     static void getSupportedExtensions(std::set<QString> &extensions);
 
+    virtual bool isUpdating() const {
+        return m_decodeThread && m_decodeThread->isRunning();
+    }
+
 protected:
     QString m_path;
     QString m_error;
 
+    OGGZ *m_oggz;
     FishSound *m_fishSound;
     QProgressDialog *m_progress;
     size_t m_fileSize;
@@ -50,6 +62,18 @@
  
     static int readPacket(OGGZ *, ogg_packet *, long, void *);
     static int acceptFrames(FishSound *, float **, long, void *);
+
+    class DecodeThread : public Thread
+    {
+    public:
+        DecodeThread(OggVorbisFileReader *reader) : m_reader(reader) { }
+        virtual void run();
+
+    protected:
+        OggVorbisFileReader *m_reader; 
+    };
+
+    DecodeThread *m_decodeThread;
 };
 
 #endif
--- a/system/Init.cpp	Thu May 10 12:48:26 2007 +0000
+++ b/system/Init.cpp	Thu May 24 16:20:22 2007 +0000
@@ -84,9 +84,9 @@
     qApp->setFont(fn);
 #else
 #ifdef Q_WS_X11
-    QFont fn = qApp->font();
-    fn.setPointSize(fn.pointSize() + 2);
-    qApp->setFont(fn);
+//    QFont fn = qApp->font();
+//    fn.setPointSize(fn.pointSize() + 2);
+//    qApp->setFont(fn);
 #endif
 #endif
 }