annotate data/fileio/CodedAudioFileReader.cpp @ 1078:ce82bcdc95d0

Fail upfront if the file is going to be too large. We expect the caller to split up large data sets into several MatrixFiles
author Chris Cannam
date Wed, 10 Jun 2015 13:10:26 +0100
parents e603b44510c3
children 4d9816ba0ebe 1517d4c60e88
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@1040 31 sv_samplerate_t targetRate,
Chris@920 32 bool normalised) :
Chris@148 33 m_cacheMode(cacheMode),
Chris@148 34 m_initialised(false),
Chris@297 35 m_serialiser(0),
Chris@297 36 m_fileRate(0),
Chris@148 37 m_cacheFileWritePtr(0),
Chris@148 38 m_cacheFileReader(0),
Chris@148 39 m_cacheWriteBuffer(0),
Chris@148 40 m_cacheWriteBufferIndex(0),
Chris@297 41 m_cacheWriteBufferSize(16384),
Chris@297 42 m_resampler(0),
Chris@757 43 m_resampleBuffer(0),
Chris@920 44 m_fileFrameCount(0),
Chris@920 45 m_normalised(normalised),
Chris@920 46 m_max(0.f),
Chris@920 47 m_gain(1.f)
Chris@148 48 {
Chris@922 49 SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl;
Chris@297 50
Chris@297 51 m_frameCount = 0;
Chris@297 52 m_sampleRate = targetRate;
Chris@148 53 }
Chris@148 54
Chris@148 55 CodedAudioFileReader::~CodedAudioFileReader()
Chris@148 56 {
Chris@263 57 QMutexLocker locker(&m_cacheMutex);
Chris@263 58
Chris@297 59 endSerialised();
Chris@297 60
Chris@148 61 if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
Chris@297 62
Chris@742 63 SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
Chris@532 64
Chris@297 65 delete m_cacheFileReader;
Chris@297 66 delete[] m_cacheWriteBuffer;
Chris@148 67
Chris@148 68 if (m_cacheFileName != "") {
Chris@290 69 if (!QFile(m_cacheFileName).remove()) {
Chris@843 70 cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl;
Chris@148 71 }
Chris@148 72 }
Chris@297 73
Chris@297 74 delete m_resampler;
Chris@297 75 delete[] m_resampleBuffer;
Chris@297 76 }
Chris@297 77
Chris@297 78 void
Chris@297 79 CodedAudioFileReader::startSerialised(QString id)
Chris@297 80 {
Chris@742 81 SVDEBUG << "CodedAudioFileReader::startSerialised(" << id << ")" << endl;
Chris@297 82
Chris@297 83 delete m_serialiser;
Chris@297 84 m_serialiser = new Serialiser(id);
Chris@297 85 }
Chris@297 86
Chris@297 87 void
Chris@297 88 CodedAudioFileReader::endSerialised()
Chris@297 89 {
Chris@844 90 SVDEBUG << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId() : "(none)") << endl;
Chris@297 91
Chris@297 92 delete m_serialiser;
Chris@297 93 m_serialiser = 0;
Chris@148 94 }
Chris@148 95
Chris@148 96 void
Chris@148 97 CodedAudioFileReader::initialiseDecodeCache()
Chris@148 98 {
Chris@263 99 QMutexLocker locker(&m_cacheMutex);
Chris@263 100
Chris@742 101 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl;
Chris@297 102
Chris@297 103 if (m_fileRate == 0) {
Chris@843 104 cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl;
Chris@754 105 throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)");
Chris@297 106 }
Chris@297 107 if (m_sampleRate == 0) {
Chris@297 108 m_sampleRate = m_fileRate;
Chris@690 109 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << endl;
Chris@297 110 }
Chris@297 111 if (m_fileRate != m_sampleRate) {
Chris@757 112 SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << endl;
Chris@297 113 m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297 114 m_channelCount,
Chris@297 115 m_cacheWriteBufferSize);
Chris@1040 116 double ratio = m_sampleRate / m_fileRate;
Chris@297 117 m_resampleBuffer = new float
Chris@1038 118 [lrint(ceil(double(m_cacheWriteBufferSize) * m_channelCount * ratio + 1))];
Chris@297 119 }
Chris@297 120
Chris@297 121 m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297 122 m_cacheWriteBufferIndex = 0;
Chris@297 123
Chris@148 124 if (m_cacheMode == CacheInTemporaryFile) {
Chris@148 125
Chris@148 126 try {
Chris@148 127 QDir dir(TempDirectory::getInstance()->getPath());
Chris@148 128 m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290 129 .arg((intptr_t)this));
Chris@148 130
Chris@148 131 SF_INFO fileInfo;
Chris@1040 132 int fileRate = int(round(m_sampleRate));
Chris@1040 133 if (m_sampleRate != sv_samplerate_t(fileRate)) {
Chris@1040 134 cerr << "CodedAudioFileReader: WARNING: Non-integer sample rate "
Chris@1040 135 << m_sampleRate << " presented for writing, rounding to " << fileRate
Chris@1040 136 << endl;
Chris@1040 137 }
Chris@1040 138 fileInfo.samplerate = fileRate;
Chris@148 139 fileInfo.channels = m_channelCount;
Chris@1040 140
Chris@297 141 // No point in writing 24-bit or float; generally this
Chris@297 142 // class is used for decoding files that have come from a
Chris@297 143 // 16 bit source or that decode to only 16 bits anyway.
Chris@297 144 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148 145
Chris@290 146 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 147 SFM_WRITE, &fileInfo);
Chris@148 148
Chris@265 149 if (m_cacheFileWritePtr) {
Chris@265 150
Chris@297 151 // Ideally we would do this now only if we were in a
Chris@297 152 // threaded mode -- creating the reader later if we're
Chris@297 153 // not threaded -- but we don't have access to that
Chris@297 154 // information here
Chris@265 155
Chris@265 156 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 157
Chris@265 158 if (!m_cacheFileReader->isOK()) {
Chris@843 159 cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
Chris@265 160 delete m_cacheFileReader;
Chris@265 161 m_cacheFileReader = 0;
Chris@265 162 m_cacheMode = CacheInMemory;
Chris@265 163 sf_close(m_cacheFileWritePtr);
Chris@265 164 }
Chris@297 165
Chris@265 166 } else {
Chris@843 167 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" << endl;
Chris@148 168 m_cacheMode = CacheInMemory;
Chris@148 169 }
Chris@265 170
Chris@148 171 } catch (DirectoryCreationFailed f) {
Chris@843 172 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
Chris@148 173 m_cacheMode = CacheInMemory;
Chris@148 174 }
Chris@148 175 }
Chris@148 176
Chris@148 177 if (m_cacheMode == CacheInMemory) {
Chris@148 178 m_data.clear();
Chris@148 179 }
Chris@148 180
Chris@148 181 m_initialised = true;
Chris@148 182 }
Chris@148 183
Chris@148 184 void
Chris@1038 185 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
Chris@148 186 {
Chris@263 187 QMutexLocker locker(&m_cacheMutex);
Chris@263 188
Chris@148 189 if (!m_initialised) return;
Chris@148 190
Chris@1038 191 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 192
Chris@929 193 for (int c = 0; c < m_channelCount; ++c) {
Chris@148 194
Chris@297 195 float sample = samples[c][i];
Chris@297 196
Chris@297 197 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 198
Chris@297 199 if (m_cacheWriteBufferIndex ==
Chris@297 200 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 201
Chris@297 202 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 203 m_cacheWriteBufferIndex = 0;
Chris@297 204 }
Chris@297 205
Chris@297 206 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 207 m_cacheFileReader) {
Chris@297 208 m_cacheFileReader->updateFrameCount();
Chris@297 209 }
Chris@297 210 }
Chris@297 211 }
Chris@297 212 }
Chris@297 213
Chris@297 214 void
Chris@1038 215 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, sv_frame_t nframes)
Chris@297 216 {
Chris@297 217 QMutexLocker locker(&m_cacheMutex);
Chris@297 218
Chris@297 219 if (!m_initialised) return;
Chris@297 220
Chris@1038 221 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 222
Chris@929 223 for (int c = 0; c < m_channelCount; ++c) {
Chris@297 224
Chris@297 225 float sample = samples[i * m_channelCount + c];
Chris@297 226
Chris@297 227 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 228
Chris@297 229 if (m_cacheWriteBufferIndex ==
Chris@297 230 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 231
Chris@297 232 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 233 m_cacheWriteBufferIndex = 0;
Chris@297 234 }
Chris@297 235
Chris@297 236 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 237 m_cacheFileReader) {
Chris@297 238 m_cacheFileReader->updateFrameCount();
Chris@297 239 }
Chris@297 240 }
Chris@297 241 }
Chris@297 242 }
Chris@297 243
Chris@297 244 void
Chris@297 245 CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297 246 {
Chris@297 247 QMutexLocker locker(&m_cacheMutex);
Chris@297 248
Chris@297 249 if (!m_initialised) return;
Chris@297 250
Chris@1038 251 for (float sample: samples) {
Chris@297 252
Chris@148 253 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 254
Chris@148 255 if (m_cacheWriteBufferIndex ==
Chris@148 256 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 257
Chris@297 258 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 259 m_cacheWriteBufferIndex = 0;
Chris@266 260 }
Chris@265 261
Chris@266 262 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 263 m_cacheFileReader) {
Chris@266 264 m_cacheFileReader->updateFrameCount();
Chris@148 265 }
Chris@148 266 }
Chris@148 267 }
Chris@148 268
Chris@148 269 void
Chris@148 270 CodedAudioFileReader::finishDecodeCache()
Chris@148 271 {
Chris@263 272 QMutexLocker locker(&m_cacheMutex);
Chris@263 273
Chris@192 274 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 275
Chris@148 276 if (!m_initialised) {
Chris@843 277 cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
Chris@148 278 return;
Chris@148 279 }
Chris@148 280
Chris@920 281 pushBuffer(m_cacheWriteBuffer,
Chris@920 282 m_cacheWriteBufferIndex / m_channelCount,
Chris@920 283 true);
Chris@297 284
Chris@297 285 delete[] m_cacheWriteBuffer;
Chris@297 286 m_cacheWriteBuffer = 0;
Chris@297 287
Chris@297 288 delete[] m_resampleBuffer;
Chris@297 289 m_resampleBuffer = 0;
Chris@297 290
Chris@297 291 delete m_resampler;
Chris@297 292 m_resampler = 0;
Chris@297 293
Chris@297 294 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 295 sf_close(m_cacheFileWritePtr);
Chris@297 296 m_cacheFileWritePtr = 0;
Chris@297 297 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297 298 }
Chris@297 299 }
Chris@297 300
Chris@297 301 void
Chris@1038 302 CodedAudioFileReader::pushBuffer(float *buffer, sv_frame_t sz, bool final)
Chris@297 303 {
Chris@757 304 m_fileFrameCount += sz;
Chris@757 305
Chris@1040 306 double ratio = 1.0;
Chris@758 307 if (m_resampler && m_fileRate != 0) {
Chris@1040 308 ratio = m_sampleRate / m_fileRate;
Chris@758 309 }
Chris@758 310
Chris@1040 311 if (ratio != 1.0) {
Chris@758 312 pushBufferResampling(buffer, sz, ratio, final);
Chris@758 313 } else {
Chris@758 314 pushBufferNonResampling(buffer, sz);
Chris@758 315 }
Chris@758 316 }
Chris@757 317
Chris@758 318 void
Chris@1038 319 CodedAudioFileReader::pushBufferNonResampling(float *buffer, sv_frame_t sz)
Chris@758 320 {
Chris@920 321 float clip = 1.0;
Chris@1038 322 sv_frame_t count = sz * m_channelCount;
Chris@318 323
Chris@920 324 if (m_normalised) {
Chris@1038 325 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 326 float v = fabsf(buffer[i]);
Chris@920 327 if (v > m_max) {
Chris@920 328 m_max = v;
Chris@920 329 m_gain = 1.f / m_max;
Chris@920 330 }
Chris@920 331 }
Chris@920 332 } else {
Chris@1038 333 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 334 if (buffer[i] > clip) buffer[i] = clip;
Chris@920 335 }
Chris@1038 336 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 337 if (buffer[i] < -clip) buffer[i] = -clip;
Chris@920 338 }
Chris@297 339 }
Chris@297 340
Chris@297 341 m_frameCount += sz;
Chris@297 342
Chris@148 343 switch (m_cacheMode) {
Chris@148 344
Chris@148 345 case CacheInTemporaryFile:
Chris@1038 346 if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
Chris@544 347 sf_close(m_cacheFileWritePtr);
Chris@544 348 m_cacheFileWritePtr = 0;
Chris@544 349 throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544 350 }
Chris@148 351 break;
Chris@148 352
Chris@148 353 case CacheInMemory:
Chris@543 354 m_dataLock.lockForWrite();
Chris@1038 355 for (sv_frame_t s = 0; s < count; ++s) {
Chris@543 356 m_data.push_back(buffer[s]);
Chris@297 357 }
Chris@543 358 m_dataLock.unlock();
Chris@148 359 break;
Chris@148 360 }
Chris@758 361 }
Chris@757 362
Chris@758 363 void
Chris@1038 364 CodedAudioFileReader::pushBufferResampling(float *buffer, sv_frame_t sz,
Chris@1038 365 double ratio, bool final)
Chris@758 366 {
Chris@759 367 SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
Chris@757 368
Chris@759 369 if (sz > 0) {
Chris@759 370
Chris@1038 371 sv_frame_t out = m_resampler->resampleInterleaved
Chris@759 372 (buffer,
Chris@759 373 m_resampleBuffer,
Chris@759 374 sz,
Chris@759 375 ratio,
Chris@759 376 false);
Chris@759 377
Chris@759 378 pushBufferNonResampling(m_resampleBuffer, out);
Chris@759 379 }
Chris@757 380
Chris@758 381 if (final) {
Chris@758 382
Chris@1038 383 sv_frame_t padFrames = 1;
Chris@1038 384 if (double(m_frameCount) / ratio < double(m_fileFrameCount)) {
Chris@1038 385 padFrames = m_fileFrameCount - sv_frame_t(double(m_frameCount) / ratio) + 1;
Chris@757 386 }
Chris@758 387
Chris@1038 388 sv_frame_t padSamples = padFrames * m_channelCount;
Chris@758 389
Chris@1038 390 SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
Chris@758 391
Chris@758 392 float *padding = new float[padSamples];
Chris@1038 393 for (sv_frame_t i = 0; i < padSamples; ++i) padding[i] = 0.f;
Chris@758 394
Chris@1038 395 sv_frame_t out = m_resampler->resampleInterleaved
Chris@758 396 (padding,
Chris@758 397 m_resampleBuffer,
Chris@758 398 padFrames,
Chris@758 399 ratio,
Chris@758 400 true);
Chris@758 401
Chris@1038 402 if (m_frameCount + out > sv_frame_t(double(m_fileFrameCount) * ratio)) {
Chris@1038 403 out = sv_frame_t(double(m_fileFrameCount) * ratio) - m_frameCount;
Chris@759 404 }
Chris@759 405
Chris@758 406 pushBufferNonResampling(m_resampleBuffer, out);
Chris@758 407 delete[] padding;
Chris@757 408 }
Chris@148 409 }
Chris@148 410
Chris@1041 411 SampleBlock
Chris@1041 412 CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 413 {
Chris@543 414 // Lock is only required in CacheInMemory mode (the cache file
Chris@543 415 // reader is expected to be thread safe and manage its own
Chris@543 416 // locking)
Chris@263 417
Chris@265 418 if (!m_initialised) {
Chris@690 419 SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
Chris@1041 420 return SampleBlock();
Chris@265 421 }
Chris@148 422
Chris@1041 423 SampleBlock frames;
Chris@1041 424
Chris@148 425 switch (m_cacheMode) {
Chris@148 426
Chris@148 427 case CacheInTemporaryFile:
Chris@148 428 if (m_cacheFileReader) {
Chris@1041 429 frames = m_cacheFileReader->getInterleavedFrames(start, count);
Chris@148 430 }
Chris@148 431 break;
Chris@148 432
Chris@148 433 case CacheInMemory:
Chris@148 434 {
Chris@1041 435 if (!isOK()) return SampleBlock();
Chris@1041 436 if (count == 0) return SampleBlock();
Chris@148 437
Chris@1038 438 sv_frame_t idx = start * m_channelCount;
Chris@1038 439 sv_frame_t i = 0;
Chris@1041 440 sv_frame_t n = count * m_channelCount;
Chris@148 441
Chris@1052 442 frames.resize(n);
Chris@1052 443
Chris@543 444 m_dataLock.lockForRead();
Chris@1041 445 while (i < n && in_range_for(m_data, idx)) {
Chris@1052 446 frames[i++] = m_data[idx++];
Chris@148 447 }
Chris@543 448 m_dataLock.unlock();
Chris@1052 449
Chris@1052 450 frames.resize(i);
Chris@148 451 }
Chris@148 452 }
Chris@920 453
Chris@920 454 if (m_normalised) {
Chris@1052 455 for (auto &f: frames) f *= m_gain;
Chris@920 456 }
Chris@1041 457
Chris@1041 458 return frames;
Chris@148 459 }
Chris@148 460