annotate data/fileio/CodedAudioFileReader.cpp @ 754:26636c46bcdf

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