annotate data/fileio/CodedAudioFileReader.cpp @ 408:115f60df1e4d

* Speed up spectrogram painting by releasing mutex in FFTDataServer while calculating data prior to writing it, and by adding whole-column value query methods to FFT objects * Add paint cache to Thumbwheel -- repaints of this widget were slowing down the whole spectrogram repaint * More uses of MutexLocker (named and with debug) and more profile points * Make startup much quicker some of the time, with OSC server in place
author Chris Cannam
date Thu, 08 May 2008 14:46:22 +0000
parents d095214ffbaf
children 76f6971c8433
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@297 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@148 8
Chris@148 9 This program is free software; you can redistribute it and/or
Chris@148 10 modify it under the terms of the GNU General Public License as
Chris@148 11 published by the Free Software Foundation; either version 2 of the
Chris@148 12 License, or (at your option) any later version. See the file
Chris@148 13 COPYING included with this distribution for more information.
Chris@148 14 */
Chris@148 15
Chris@148 16 #include "CodedAudioFileReader.h"
Chris@148 17
Chris@148 18 #include "WavFileReader.h"
Chris@148 19 #include "base/TempDirectory.h"
Chris@148 20 #include "base/Exceptions.h"
Chris@192 21 #include "base/Profiler.h"
Chris@297 22 #include "base/Serialiser.h"
Chris@297 23 #include "base/Resampler.h"
Chris@148 24
Chris@148 25 #include <iostream>
Chris@148 26 #include <QDir>
Chris@263 27 #include <QMutexLocker>
Chris@148 28
Chris@297 29 CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
Chris@297 30 size_t targetRate) :
Chris@148 31 m_cacheMode(cacheMode),
Chris@148 32 m_initialised(false),
Chris@297 33 m_serialiser(0),
Chris@297 34 m_fileRate(0),
Chris@148 35 m_cacheFileWritePtr(0),
Chris@148 36 m_cacheFileReader(0),
Chris@148 37 m_cacheWriteBuffer(0),
Chris@148 38 m_cacheWriteBufferIndex(0),
Chris@297 39 m_cacheWriteBufferSize(16384),
Chris@297 40 m_resampler(0),
Chris@297 41 m_resampleBuffer(0)
Chris@148 42 {
Chris@327 43 // std::cerr << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << std::endl;
Chris@297 44
Chris@297 45 m_frameCount = 0;
Chris@297 46 m_sampleRate = targetRate;
Chris@148 47 }
Chris@148 48
Chris@148 49 CodedAudioFileReader::~CodedAudioFileReader()
Chris@148 50 {
Chris@263 51 QMutexLocker locker(&m_cacheMutex);
Chris@263 52
Chris@297 53 endSerialised();
Chris@297 54
Chris@148 55 if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
Chris@297 56
Chris@297 57 delete m_cacheFileReader;
Chris@297 58 delete[] m_cacheWriteBuffer;
Chris@148 59
Chris@148 60 if (m_cacheFileName != "") {
Chris@290 61 if (!QFile(m_cacheFileName).remove()) {
Chris@290 62 std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName.toStdString() << "\"" << std::endl;
Chris@148 63 }
Chris@148 64 }
Chris@297 65
Chris@297 66 delete m_resampler;
Chris@297 67 delete[] m_resampleBuffer;
Chris@297 68 }
Chris@297 69
Chris@297 70 void
Chris@297 71 CodedAudioFileReader::startSerialised(QString id)
Chris@297 72 {
Chris@297 73 // std::cerr << "CodedAudioFileReader::startSerialised(" << id.toStdString() << ")" << std::endl;
Chris@297 74
Chris@297 75 delete m_serialiser;
Chris@297 76 m_serialiser = new Serialiser(id);
Chris@297 77 }
Chris@297 78
Chris@297 79 void
Chris@297 80 CodedAudioFileReader::endSerialised()
Chris@297 81 {
Chris@406 82 // std::cerr << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId().toStdString() : "(none)") << std::endl;
Chris@297 83
Chris@297 84 delete m_serialiser;
Chris@297 85 m_serialiser = 0;
Chris@148 86 }
Chris@148 87
Chris@148 88 void
Chris@148 89 CodedAudioFileReader::initialiseDecodeCache()
Chris@148 90 {
Chris@263 91 QMutexLocker locker(&m_cacheMutex);
Chris@263 92
Chris@297 93 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << std::endl;
Chris@297 94
Chris@297 95 if (m_fileRate == 0) {
Chris@297 96 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << std::endl;
Chris@297 97 m_fileRate = 48000; // got to have something
Chris@297 98 }
Chris@297 99 if (m_sampleRate == 0) {
Chris@297 100 m_sampleRate = m_fileRate;
Chris@386 101 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << std::endl;
Chris@297 102 }
Chris@297 103 if (m_fileRate != m_sampleRate) {
Chris@297 104 std::cerr << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << std::endl;
Chris@297 105 m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297 106 m_channelCount,
Chris@297 107 m_cacheWriteBufferSize);
Chris@297 108 float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297 109 m_resampleBuffer = new float
Chris@398 110 [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio + 1))];
Chris@297 111 }
Chris@297 112
Chris@297 113 m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297 114 m_cacheWriteBufferIndex = 0;
Chris@297 115
Chris@148 116 if (m_cacheMode == CacheInTemporaryFile) {
Chris@148 117
Chris@148 118 try {
Chris@148 119 QDir dir(TempDirectory::getInstance()->getPath());
Chris@148 120 m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290 121 .arg((intptr_t)this));
Chris@148 122
Chris@148 123 SF_INFO fileInfo;
Chris@148 124 fileInfo.samplerate = m_sampleRate;
Chris@148 125 fileInfo.channels = m_channelCount;
Chris@297 126
Chris@297 127 // No point in writing 24-bit or float; generally this
Chris@297 128 // class is used for decoding files that have come from a
Chris@297 129 // 16 bit source or that decode to only 16 bits anyway.
Chris@297 130 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148 131
Chris@290 132 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 133 SFM_WRITE, &fileInfo);
Chris@148 134
Chris@265 135 if (m_cacheFileWritePtr) {
Chris@265 136
Chris@297 137 // Ideally we would do this now only if we were in a
Chris@297 138 // threaded mode -- creating the reader later if we're
Chris@297 139 // not threaded -- but we don't have access to that
Chris@297 140 // information here
Chris@265 141
Chris@265 142 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 143
Chris@265 144 if (!m_cacheFileReader->isOK()) {
Chris@290 145 std::cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError().toStdString() << std::endl;
Chris@265 146 delete m_cacheFileReader;
Chris@265 147 m_cacheFileReader = 0;
Chris@265 148 m_cacheMode = CacheInMemory;
Chris@265 149 sf_close(m_cacheFileWritePtr);
Chris@265 150 }
Chris@297 151
Chris@265 152 } else {
Chris@290 153 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName.toStdString() << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << std::endl;
Chris@148 154 m_cacheMode = CacheInMemory;
Chris@148 155 }
Chris@265 156
Chris@148 157 } catch (DirectoryCreationFailed f) {
Chris@148 158 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << std::endl;
Chris@148 159 m_cacheMode = CacheInMemory;
Chris@148 160 }
Chris@148 161 }
Chris@148 162
Chris@148 163 if (m_cacheMode == CacheInMemory) {
Chris@148 164 m_data.clear();
Chris@148 165 }
Chris@148 166
Chris@148 167 m_initialised = true;
Chris@148 168 }
Chris@148 169
Chris@148 170 void
Chris@297 171 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, size_t nframes)
Chris@148 172 {
Chris@263 173 QMutexLocker locker(&m_cacheMutex);
Chris@263 174
Chris@148 175 if (!m_initialised) return;
Chris@148 176
Chris@297 177 for (size_t i = 0; i < nframes; ++i) {
Chris@297 178
Chris@297 179 for (size_t c = 0; c < m_channelCount; ++c) {
Chris@148 180
Chris@297 181 float sample = samples[c][i];
Chris@297 182
Chris@297 183 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 184
Chris@297 185 if (m_cacheWriteBufferIndex ==
Chris@297 186 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 187
Chris@297 188 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 189 m_cacheWriteBufferIndex = 0;
Chris@297 190 }
Chris@297 191
Chris@297 192 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 193 m_cacheFileReader) {
Chris@297 194 m_cacheFileReader->updateFrameCount();
Chris@297 195 }
Chris@297 196 }
Chris@297 197 }
Chris@297 198 }
Chris@297 199
Chris@297 200 void
Chris@297 201 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, size_t nframes)
Chris@297 202 {
Chris@297 203 QMutexLocker locker(&m_cacheMutex);
Chris@297 204
Chris@297 205 if (!m_initialised) return;
Chris@297 206
Chris@297 207 for (size_t i = 0; i < nframes; ++i) {
Chris@297 208
Chris@297 209 for (size_t c = 0; c < m_channelCount; ++c) {
Chris@297 210
Chris@297 211 float sample = samples[i * m_channelCount + c];
Chris@297 212
Chris@297 213 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 214
Chris@297 215 if (m_cacheWriteBufferIndex ==
Chris@297 216 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 217
Chris@297 218 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 219 m_cacheWriteBufferIndex = 0;
Chris@297 220 }
Chris@297 221
Chris@297 222 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 223 m_cacheFileReader) {
Chris@297 224 m_cacheFileReader->updateFrameCount();
Chris@297 225 }
Chris@297 226 }
Chris@297 227 }
Chris@297 228 }
Chris@297 229
Chris@297 230 void
Chris@297 231 CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297 232 {
Chris@297 233 QMutexLocker locker(&m_cacheMutex);
Chris@297 234
Chris@297 235 if (!m_initialised) return;
Chris@297 236
Chris@297 237 for (size_t i = 0; i < samples.size(); ++i) {
Chris@297 238
Chris@297 239 float sample = samples[i];
Chris@297 240
Chris@148 241 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 242
Chris@148 243 if (m_cacheWriteBufferIndex ==
Chris@148 244 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 245
Chris@297 246 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 247 m_cacheWriteBufferIndex = 0;
Chris@266 248 }
Chris@265 249
Chris@266 250 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 251 m_cacheFileReader) {
Chris@266 252 m_cacheFileReader->updateFrameCount();
Chris@148 253 }
Chris@148 254 }
Chris@148 255 }
Chris@148 256
Chris@148 257 void
Chris@148 258 CodedAudioFileReader::finishDecodeCache()
Chris@148 259 {
Chris@263 260 QMutexLocker locker(&m_cacheMutex);
Chris@263 261
Chris@192 262 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 263
Chris@148 264 if (!m_initialised) {
Chris@148 265 std::cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << std::endl;
Chris@148 266 return;
Chris@148 267 }
Chris@148 268
Chris@297 269 if (m_cacheWriteBufferIndex > 0) {
Chris@297 270 //!!! check for return value! out of disk space, etc!
Chris@297 271 pushBuffer(m_cacheWriteBuffer,
Chris@297 272 m_cacheWriteBufferIndex / m_channelCount,
Chris@297 273 true);
Chris@297 274 }
Chris@297 275
Chris@297 276 delete[] m_cacheWriteBuffer;
Chris@297 277 m_cacheWriteBuffer = 0;
Chris@297 278
Chris@297 279 delete[] m_resampleBuffer;
Chris@297 280 m_resampleBuffer = 0;
Chris@297 281
Chris@297 282 delete m_resampler;
Chris@297 283 m_resampler = 0;
Chris@297 284
Chris@297 285 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 286 sf_close(m_cacheFileWritePtr);
Chris@297 287 m_cacheFileWritePtr = 0;
Chris@297 288 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297 289 }
Chris@297 290 }
Chris@297 291
Chris@297 292 void
Chris@297 293 CodedAudioFileReader::pushBuffer(float *buffer, size_t sz, bool final)
Chris@297 294 {
Chris@322 295 float max = 1.0;
Chris@322 296 size_t count = sz * m_channelCount;
Chris@318 297
Chris@375 298 if (m_resampler && m_fileRate != 0) {
Chris@297 299
Chris@297 300 float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297 301
Chris@297 302 if (ratio != 1.f) {
Chris@322 303
Chris@297 304 size_t out = m_resampler->resampleInterleaved
Chris@297 305 (buffer,
Chris@297 306 m_resampleBuffer,
Chris@297 307 sz,
Chris@297 308 ratio,
Chris@297 309 final);
Chris@297 310
Chris@297 311 buffer = m_resampleBuffer;
Chris@297 312 sz = out;
Chris@322 313 count = sz * m_channelCount;
Chris@297 314 }
Chris@322 315 }
Chris@318 316
Chris@322 317 for (size_t i = 0; i < count; ++i) {
Chris@322 318 if (buffer[i] > max) buffer[i] = max;
Chris@322 319 }
Chris@322 320 for (size_t i = 0; i < count; ++i) {
Chris@322 321 if (buffer[i] < -max) buffer[i] = -max;
Chris@297 322 }
Chris@297 323
Chris@297 324 m_frameCount += sz;
Chris@297 325
Chris@148 326 switch (m_cacheMode) {
Chris@148 327
Chris@148 328 case CacheInTemporaryFile:
Chris@297 329 //!!! check for return value! out of disk space, etc!
Chris@297 330 sf_writef_float(m_cacheFileWritePtr, buffer, sz);
Chris@148 331 break;
Chris@148 332
Chris@148 333 case CacheInMemory:
Chris@322 334 for (size_t s = 0; s < count; ++s) {
Chris@322 335 m_data.push_back(buffer[count]);
Chris@297 336 }
Chris@297 337 MUNLOCK_SAMPLEBLOCK(m_data);
Chris@148 338 break;
Chris@148 339 }
Chris@148 340 }
Chris@148 341
Chris@148 342 void
Chris@148 343 CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count,
Chris@148 344 SampleBlock &frames) const
Chris@148 345 {
Chris@263 346 //!!! we want to ensure this doesn't require a lock -- at the
Chris@263 347 // moment it does need one, but it doesn't have one...
Chris@263 348
Chris@265 349 if (!m_initialised) {
Chris@265 350 std::cerr << "CodedAudioFileReader::getInterleavedFrames: not initialised" << std::endl;
Chris@265 351 return;
Chris@265 352 }
Chris@148 353
Chris@148 354 switch (m_cacheMode) {
Chris@148 355
Chris@148 356 case CacheInTemporaryFile:
Chris@148 357 if (m_cacheFileReader) {
Chris@148 358 m_cacheFileReader->getInterleavedFrames(start, count, frames);
Chris@148 359 }
Chris@148 360 break;
Chris@148 361
Chris@148 362 case CacheInMemory:
Chris@148 363 {
Chris@148 364 frames.clear();
Chris@148 365 if (!isOK()) return;
Chris@148 366 if (count == 0) return;
Chris@148 367
Chris@148 368 // slownessabounds
Chris@148 369
Chris@148 370 for (size_t i = start; i < start + count; ++i) {
Chris@148 371 for (size_t ch = 0; ch < m_channelCount; ++ch) {
Chris@148 372 size_t index = i * m_channelCount + ch;
Chris@148 373 if (index >= m_data.size()) return;
Chris@148 374 frames.push_back(m_data[index]);
Chris@148 375 }
Chris@148 376 }
Chris@148 377 }
Chris@148 378 }
Chris@148 379 }
Chris@148 380