annotate data/fileio/CodedAudioFileReader.cpp @ 1124:efea94b04d5a 3.0-integration

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