annotate data/fileio/CodedAudioFileReader.cpp @ 661:a4faa1840384

* If a FileSource URL won't convert at all in strict mode, try again in tolerant mode (necessary for e.g. filenames with square brackets in them)
author Chris Cannam
date Tue, 19 Oct 2010 21:47:55 +0100
parents 939ffbc32d50
children 029dd9e5cc29 b4a8d8221eaf
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@598 57 // std::cerr << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << std::endl;
Chris@532 58
Chris@297 59 delete m_cacheFileReader;
Chris@297 60 delete[] m_cacheWriteBuffer;
Chris@148 61
Chris@148 62 if (m_cacheFileName != "") {
Chris@290 63 if (!QFile(m_cacheFileName).remove()) {
Chris@290 64 std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName.toStdString() << "\"" << std::endl;
Chris@148 65 }
Chris@148 66 }
Chris@297 67
Chris@297 68 delete m_resampler;
Chris@297 69 delete[] m_resampleBuffer;
Chris@297 70 }
Chris@297 71
Chris@297 72 void
Chris@297 73 CodedAudioFileReader::startSerialised(QString id)
Chris@297 74 {
Chris@297 75 // std::cerr << "CodedAudioFileReader::startSerialised(" << id.toStdString() << ")" << std::endl;
Chris@297 76
Chris@297 77 delete m_serialiser;
Chris@297 78 m_serialiser = new Serialiser(id);
Chris@297 79 }
Chris@297 80
Chris@297 81 void
Chris@297 82 CodedAudioFileReader::endSerialised()
Chris@297 83 {
Chris@406 84 // std::cerr << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId().toStdString() : "(none)") << std::endl;
Chris@297 85
Chris@297 86 delete m_serialiser;
Chris@297 87 m_serialiser = 0;
Chris@148 88 }
Chris@148 89
Chris@148 90 void
Chris@148 91 CodedAudioFileReader::initialiseDecodeCache()
Chris@148 92 {
Chris@263 93 QMutexLocker locker(&m_cacheMutex);
Chris@263 94
Chris@522 95 // std::cerr << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << std::endl;
Chris@297 96
Chris@297 97 if (m_fileRate == 0) {
Chris@297 98 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << std::endl;
Chris@297 99 m_fileRate = 48000; // got to have something
Chris@297 100 }
Chris@297 101 if (m_sampleRate == 0) {
Chris@297 102 m_sampleRate = m_fileRate;
Chris@386 103 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << std::endl;
Chris@297 104 }
Chris@297 105 if (m_fileRate != m_sampleRate) {
Chris@297 106 std::cerr << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << std::endl;
Chris@297 107 m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297 108 m_channelCount,
Chris@297 109 m_cacheWriteBufferSize);
Chris@297 110 float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297 111 m_resampleBuffer = new float
Chris@398 112 [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio + 1))];
Chris@297 113 }
Chris@297 114
Chris@297 115 m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297 116 m_cacheWriteBufferIndex = 0;
Chris@297 117
Chris@148 118 if (m_cacheMode == CacheInTemporaryFile) {
Chris@148 119
Chris@148 120 try {
Chris@148 121 QDir dir(TempDirectory::getInstance()->getPath());
Chris@148 122 m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290 123 .arg((intptr_t)this));
Chris@148 124
Chris@148 125 SF_INFO fileInfo;
Chris@148 126 fileInfo.samplerate = m_sampleRate;
Chris@148 127 fileInfo.channels = m_channelCount;
Chris@297 128
Chris@297 129 // No point in writing 24-bit or float; generally this
Chris@297 130 // class is used for decoding files that have come from a
Chris@297 131 // 16 bit source or that decode to only 16 bits anyway.
Chris@297 132 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148 133
Chris@290 134 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 135 SFM_WRITE, &fileInfo);
Chris@148 136
Chris@265 137 if (m_cacheFileWritePtr) {
Chris@265 138
Chris@297 139 // Ideally we would do this now only if we were in a
Chris@297 140 // threaded mode -- creating the reader later if we're
Chris@297 141 // not threaded -- but we don't have access to that
Chris@297 142 // information here
Chris@265 143
Chris@265 144 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 145
Chris@265 146 if (!m_cacheFileReader->isOK()) {
Chris@290 147 std::cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError().toStdString() << std::endl;
Chris@265 148 delete m_cacheFileReader;
Chris@265 149 m_cacheFileReader = 0;
Chris@265 150 m_cacheMode = CacheInMemory;
Chris@265 151 sf_close(m_cacheFileWritePtr);
Chris@265 152 }
Chris@297 153
Chris@265 154 } else {
Chris@290 155 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 156 m_cacheMode = CacheInMemory;
Chris@148 157 }
Chris@265 158
Chris@148 159 } catch (DirectoryCreationFailed f) {
Chris@148 160 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << std::endl;
Chris@148 161 m_cacheMode = CacheInMemory;
Chris@148 162 }
Chris@148 163 }
Chris@148 164
Chris@148 165 if (m_cacheMode == CacheInMemory) {
Chris@148 166 m_data.clear();
Chris@148 167 }
Chris@148 168
Chris@148 169 m_initialised = true;
Chris@148 170 }
Chris@148 171
Chris@148 172 void
Chris@297 173 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, size_t nframes)
Chris@148 174 {
Chris@263 175 QMutexLocker locker(&m_cacheMutex);
Chris@263 176
Chris@148 177 if (!m_initialised) return;
Chris@148 178
Chris@297 179 for (size_t i = 0; i < nframes; ++i) {
Chris@297 180
Chris@297 181 for (size_t c = 0; c < m_channelCount; ++c) {
Chris@148 182
Chris@297 183 float sample = samples[c][i];
Chris@297 184
Chris@297 185 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 186
Chris@297 187 if (m_cacheWriteBufferIndex ==
Chris@297 188 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 189
Chris@297 190 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 191 m_cacheWriteBufferIndex = 0;
Chris@297 192 }
Chris@297 193
Chris@297 194 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 195 m_cacheFileReader) {
Chris@297 196 m_cacheFileReader->updateFrameCount();
Chris@297 197 }
Chris@297 198 }
Chris@297 199 }
Chris@297 200 }
Chris@297 201
Chris@297 202 void
Chris@297 203 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, size_t nframes)
Chris@297 204 {
Chris@297 205 QMutexLocker locker(&m_cacheMutex);
Chris@297 206
Chris@297 207 if (!m_initialised) return;
Chris@297 208
Chris@297 209 for (size_t i = 0; i < nframes; ++i) {
Chris@297 210
Chris@297 211 for (size_t c = 0; c < m_channelCount; ++c) {
Chris@297 212
Chris@297 213 float sample = samples[i * m_channelCount + c];
Chris@297 214
Chris@297 215 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 216
Chris@297 217 if (m_cacheWriteBufferIndex ==
Chris@297 218 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 219
Chris@297 220 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 221 m_cacheWriteBufferIndex = 0;
Chris@297 222 }
Chris@297 223
Chris@297 224 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 225 m_cacheFileReader) {
Chris@297 226 m_cacheFileReader->updateFrameCount();
Chris@297 227 }
Chris@297 228 }
Chris@297 229 }
Chris@297 230 }
Chris@297 231
Chris@297 232 void
Chris@297 233 CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297 234 {
Chris@297 235 QMutexLocker locker(&m_cacheMutex);
Chris@297 236
Chris@297 237 if (!m_initialised) return;
Chris@297 238
Chris@297 239 for (size_t i = 0; i < samples.size(); ++i) {
Chris@297 240
Chris@297 241 float sample = samples[i];
Chris@297 242
Chris@148 243 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 244
Chris@148 245 if (m_cacheWriteBufferIndex ==
Chris@148 246 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 247
Chris@297 248 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 249 m_cacheWriteBufferIndex = 0;
Chris@266 250 }
Chris@265 251
Chris@266 252 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 253 m_cacheFileReader) {
Chris@266 254 m_cacheFileReader->updateFrameCount();
Chris@148 255 }
Chris@148 256 }
Chris@148 257 }
Chris@148 258
Chris@148 259 void
Chris@148 260 CodedAudioFileReader::finishDecodeCache()
Chris@148 261 {
Chris@263 262 QMutexLocker locker(&m_cacheMutex);
Chris@263 263
Chris@192 264 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 265
Chris@148 266 if (!m_initialised) {
Chris@148 267 std::cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << std::endl;
Chris@148 268 return;
Chris@148 269 }
Chris@148 270
Chris@297 271 if (m_cacheWriteBufferIndex > 0) {
Chris@297 272 pushBuffer(m_cacheWriteBuffer,
Chris@297 273 m_cacheWriteBufferIndex / m_channelCount,
Chris@297 274 true);
Chris@297 275 }
Chris@297 276
Chris@297 277 delete[] m_cacheWriteBuffer;
Chris@297 278 m_cacheWriteBuffer = 0;
Chris@297 279
Chris@297 280 delete[] m_resampleBuffer;
Chris@297 281 m_resampleBuffer = 0;
Chris@297 282
Chris@297 283 delete m_resampler;
Chris@297 284 m_resampler = 0;
Chris@297 285
Chris@297 286 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 287 sf_close(m_cacheFileWritePtr);
Chris@297 288 m_cacheFileWritePtr = 0;
Chris@297 289 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297 290 }
Chris@297 291 }
Chris@297 292
Chris@297 293 void
Chris@297 294 CodedAudioFileReader::pushBuffer(float *buffer, size_t sz, bool final)
Chris@297 295 {
Chris@322 296 float max = 1.0;
Chris@322 297 size_t count = sz * m_channelCount;
Chris@318 298
Chris@375 299 if (m_resampler && m_fileRate != 0) {
Chris@297 300
Chris@297 301 float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297 302
Chris@297 303 if (ratio != 1.f) {
Chris@322 304
Chris@297 305 size_t out = m_resampler->resampleInterleaved
Chris@297 306 (buffer,
Chris@297 307 m_resampleBuffer,
Chris@297 308 sz,
Chris@297 309 ratio,
Chris@297 310 final);
Chris@297 311
Chris@297 312 buffer = m_resampleBuffer;
Chris@297 313 sz = out;
Chris@322 314 count = sz * m_channelCount;
Chris@297 315 }
Chris@322 316 }
Chris@318 317
Chris@322 318 for (size_t i = 0; i < count; ++i) {
Chris@322 319 if (buffer[i] > max) buffer[i] = max;
Chris@322 320 }
Chris@322 321 for (size_t i = 0; i < count; ++i) {
Chris@322 322 if (buffer[i] < -max) buffer[i] = -max;
Chris@297 323 }
Chris@297 324
Chris@297 325 m_frameCount += sz;
Chris@297 326
Chris@148 327 switch (m_cacheMode) {
Chris@148 328
Chris@148 329 case CacheInTemporaryFile:
Chris@544 330 if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
Chris@544 331 sf_close(m_cacheFileWritePtr);
Chris@544 332 m_cacheFileWritePtr = 0;
Chris@544 333 throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544 334 }
Chris@148 335 break;
Chris@148 336
Chris@148 337 case CacheInMemory:
Chris@543 338 m_dataLock.lockForWrite();
Chris@322 339 for (size_t s = 0; s < count; ++s) {
Chris@543 340 m_data.push_back(buffer[s]);
Chris@297 341 }
Chris@297 342 MUNLOCK_SAMPLEBLOCK(m_data);
Chris@543 343 m_dataLock.unlock();
Chris@148 344 break;
Chris@148 345 }
Chris@148 346 }
Chris@148 347
Chris@148 348 void
Chris@148 349 CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count,
Chris@148 350 SampleBlock &frames) const
Chris@148 351 {
Chris@543 352 // Lock is only required in CacheInMemory mode (the cache file
Chris@543 353 // reader is expected to be thread safe and manage its own
Chris@543 354 // locking)
Chris@263 355
Chris@265 356 if (!m_initialised) {
Chris@265 357 std::cerr << "CodedAudioFileReader::getInterleavedFrames: not initialised" << std::endl;
Chris@265 358 return;
Chris@265 359 }
Chris@148 360
Chris@148 361 switch (m_cacheMode) {
Chris@148 362
Chris@148 363 case CacheInTemporaryFile:
Chris@148 364 if (m_cacheFileReader) {
Chris@148 365 m_cacheFileReader->getInterleavedFrames(start, count, frames);
Chris@148 366 }
Chris@148 367 break;
Chris@148 368
Chris@148 369 case CacheInMemory:
Chris@148 370 {
Chris@148 371 frames.clear();
Chris@148 372 if (!isOK()) return;
Chris@148 373 if (count == 0) return;
Chris@543 374 frames.reserve(count * m_channelCount);
Chris@148 375
Chris@543 376 size_t idx = start * m_channelCount;
Chris@543 377 size_t i = 0;
Chris@148 378
Chris@543 379 m_dataLock.lockForRead();
Chris@543 380 while (i < count * m_channelCount && idx < m_data.size()) {
Chris@543 381 frames.push_back(m_data[idx]);
Chris@543 382 ++idx;
Chris@148 383 }
Chris@543 384 m_dataLock.unlock();
Chris@148 385 }
Chris@148 386 }
Chris@148 387 }
Chris@148 388