annotate data/fileio/CodedAudioFileReader.cpp @ 1096:4d9816ba0ebe simple-fft-model

Rework audio file reader API to prefer using std containers
author Chris Cannam
date Mon, 15 Jun 2015 12:19:47 +0100
parents e603b44510c3
children 329ddaf7415d
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@1096 30 using namespace std;
Chris@1096 31
Chris@297 32 CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
Chris@1040 33 sv_samplerate_t targetRate,
Chris@920 34 bool normalised) :
Chris@148 35 m_cacheMode(cacheMode),
Chris@148 36 m_initialised(false),
Chris@297 37 m_serialiser(0),
Chris@297 38 m_fileRate(0),
Chris@148 39 m_cacheFileWritePtr(0),
Chris@148 40 m_cacheFileReader(0),
Chris@148 41 m_cacheWriteBuffer(0),
Chris@148 42 m_cacheWriteBufferIndex(0),
Chris@297 43 m_cacheWriteBufferSize(16384),
Chris@297 44 m_resampler(0),
Chris@757 45 m_resampleBuffer(0),
Chris@920 46 m_fileFrameCount(0),
Chris@920 47 m_normalised(normalised),
Chris@920 48 m_max(0.f),
Chris@920 49 m_gain(1.f)
Chris@148 50 {
Chris@922 51 SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl;
Chris@297 52
Chris@297 53 m_frameCount = 0;
Chris@297 54 m_sampleRate = targetRate;
Chris@148 55 }
Chris@148 56
Chris@148 57 CodedAudioFileReader::~CodedAudioFileReader()
Chris@148 58 {
Chris@263 59 QMutexLocker locker(&m_cacheMutex);
Chris@263 60
Chris@297 61 endSerialised();
Chris@297 62
Chris@148 63 if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
Chris@297 64
Chris@742 65 SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
Chris@532 66
Chris@297 67 delete m_cacheFileReader;
Chris@297 68 delete[] m_cacheWriteBuffer;
Chris@148 69
Chris@148 70 if (m_cacheFileName != "") {
Chris@290 71 if (!QFile(m_cacheFileName).remove()) {
Chris@843 72 cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl;
Chris@148 73 }
Chris@148 74 }
Chris@297 75
Chris@297 76 delete m_resampler;
Chris@297 77 delete[] m_resampleBuffer;
Chris@297 78 }
Chris@297 79
Chris@297 80 void
Chris@297 81 CodedAudioFileReader::startSerialised(QString id)
Chris@297 82 {
Chris@742 83 SVDEBUG << "CodedAudioFileReader::startSerialised(" << id << ")" << endl;
Chris@297 84
Chris@297 85 delete m_serialiser;
Chris@297 86 m_serialiser = new Serialiser(id);
Chris@297 87 }
Chris@297 88
Chris@297 89 void
Chris@297 90 CodedAudioFileReader::endSerialised()
Chris@297 91 {
Chris@844 92 SVDEBUG << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId() : "(none)") << endl;
Chris@297 93
Chris@297 94 delete m_serialiser;
Chris@297 95 m_serialiser = 0;
Chris@148 96 }
Chris@148 97
Chris@148 98 void
Chris@148 99 CodedAudioFileReader::initialiseDecodeCache()
Chris@148 100 {
Chris@263 101 QMutexLocker locker(&m_cacheMutex);
Chris@263 102
Chris@742 103 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl;
Chris@297 104
Chris@297 105 if (m_fileRate == 0) {
Chris@843 106 cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl;
Chris@754 107 throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)");
Chris@297 108 }
Chris@297 109 if (m_sampleRate == 0) {
Chris@297 110 m_sampleRate = m_fileRate;
Chris@690 111 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << endl;
Chris@297 112 }
Chris@297 113 if (m_fileRate != m_sampleRate) {
Chris@757 114 SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << endl;
Chris@297 115 m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297 116 m_channelCount,
Chris@297 117 m_cacheWriteBufferSize);
Chris@1040 118 double ratio = m_sampleRate / m_fileRate;
Chris@297 119 m_resampleBuffer = new float
Chris@1038 120 [lrint(ceil(double(m_cacheWriteBufferSize) * m_channelCount * ratio + 1))];
Chris@297 121 }
Chris@297 122
Chris@297 123 m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297 124 m_cacheWriteBufferIndex = 0;
Chris@297 125
Chris@148 126 if (m_cacheMode == CacheInTemporaryFile) {
Chris@148 127
Chris@148 128 try {
Chris@148 129 QDir dir(TempDirectory::getInstance()->getPath());
Chris@148 130 m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290 131 .arg((intptr_t)this));
Chris@148 132
Chris@148 133 SF_INFO fileInfo;
Chris@1040 134 int fileRate = int(round(m_sampleRate));
Chris@1040 135 if (m_sampleRate != sv_samplerate_t(fileRate)) {
Chris@1040 136 cerr << "CodedAudioFileReader: WARNING: Non-integer sample rate "
Chris@1040 137 << m_sampleRate << " presented for writing, rounding to " << fileRate
Chris@1040 138 << endl;
Chris@1040 139 }
Chris@1040 140 fileInfo.samplerate = fileRate;
Chris@148 141 fileInfo.channels = m_channelCount;
Chris@1040 142
Chris@297 143 // No point in writing 24-bit or float; generally this
Chris@297 144 // class is used for decoding files that have come from a
Chris@297 145 // 16 bit source or that decode to only 16 bits anyway.
Chris@297 146 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148 147
Chris@290 148 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 149 SFM_WRITE, &fileInfo);
Chris@148 150
Chris@265 151 if (m_cacheFileWritePtr) {
Chris@265 152
Chris@297 153 // Ideally we would do this now only if we were in a
Chris@297 154 // threaded mode -- creating the reader later if we're
Chris@297 155 // not threaded -- but we don't have access to that
Chris@297 156 // information here
Chris@265 157
Chris@265 158 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 159
Chris@265 160 if (!m_cacheFileReader->isOK()) {
Chris@843 161 cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
Chris@265 162 delete m_cacheFileReader;
Chris@265 163 m_cacheFileReader = 0;
Chris@265 164 m_cacheMode = CacheInMemory;
Chris@265 165 sf_close(m_cacheFileWritePtr);
Chris@265 166 }
Chris@297 167
Chris@265 168 } else {
Chris@843 169 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 170 m_cacheMode = CacheInMemory;
Chris@148 171 }
Chris@265 172
Chris@148 173 } catch (DirectoryCreationFailed f) {
Chris@843 174 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
Chris@148 175 m_cacheMode = CacheInMemory;
Chris@148 176 }
Chris@148 177 }
Chris@148 178
Chris@148 179 if (m_cacheMode == CacheInMemory) {
Chris@148 180 m_data.clear();
Chris@148 181 }
Chris@148 182
Chris@148 183 m_initialised = true;
Chris@148 184 }
Chris@148 185
Chris@148 186 void
Chris@1038 187 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
Chris@148 188 {
Chris@263 189 QMutexLocker locker(&m_cacheMutex);
Chris@263 190
Chris@148 191 if (!m_initialised) return;
Chris@148 192
Chris@1038 193 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 194
Chris@929 195 for (int c = 0; c < m_channelCount; ++c) {
Chris@148 196
Chris@297 197 float sample = samples[c][i];
Chris@297 198
Chris@297 199 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 200
Chris@297 201 if (m_cacheWriteBufferIndex ==
Chris@297 202 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 203
Chris@297 204 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 205 m_cacheWriteBufferIndex = 0;
Chris@297 206 }
Chris@297 207
Chris@297 208 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 209 m_cacheFileReader) {
Chris@297 210 m_cacheFileReader->updateFrameCount();
Chris@297 211 }
Chris@297 212 }
Chris@297 213 }
Chris@297 214 }
Chris@297 215
Chris@297 216 void
Chris@1038 217 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, sv_frame_t nframes)
Chris@297 218 {
Chris@297 219 QMutexLocker locker(&m_cacheMutex);
Chris@297 220
Chris@297 221 if (!m_initialised) return;
Chris@297 222
Chris@1038 223 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 224
Chris@929 225 for (int c = 0; c < m_channelCount; ++c) {
Chris@297 226
Chris@297 227 float sample = samples[i * m_channelCount + c];
Chris@297 228
Chris@297 229 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 230
Chris@297 231 if (m_cacheWriteBufferIndex ==
Chris@297 232 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 233
Chris@297 234 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 235 m_cacheWriteBufferIndex = 0;
Chris@297 236 }
Chris@297 237
Chris@297 238 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 239 m_cacheFileReader) {
Chris@297 240 m_cacheFileReader->updateFrameCount();
Chris@297 241 }
Chris@297 242 }
Chris@297 243 }
Chris@297 244 }
Chris@297 245
Chris@297 246 void
Chris@1096 247 CodedAudioFileReader::addSamplesToDecodeCache(const vector<float> &samples)
Chris@297 248 {
Chris@297 249 QMutexLocker locker(&m_cacheMutex);
Chris@297 250
Chris@297 251 if (!m_initialised) return;
Chris@297 252
Chris@1038 253 for (float sample: samples) {
Chris@297 254
Chris@148 255 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 256
Chris@148 257 if (m_cacheWriteBufferIndex ==
Chris@148 258 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 259
Chris@297 260 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 261 m_cacheWriteBufferIndex = 0;
Chris@266 262 }
Chris@265 263
Chris@266 264 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 265 m_cacheFileReader) {
Chris@266 266 m_cacheFileReader->updateFrameCount();
Chris@148 267 }
Chris@148 268 }
Chris@148 269 }
Chris@148 270
Chris@148 271 void
Chris@148 272 CodedAudioFileReader::finishDecodeCache()
Chris@148 273 {
Chris@263 274 QMutexLocker locker(&m_cacheMutex);
Chris@263 275
Chris@192 276 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 277
Chris@148 278 if (!m_initialised) {
Chris@843 279 cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
Chris@148 280 return;
Chris@148 281 }
Chris@148 282
Chris@920 283 pushBuffer(m_cacheWriteBuffer,
Chris@920 284 m_cacheWriteBufferIndex / m_channelCount,
Chris@920 285 true);
Chris@297 286
Chris@297 287 delete[] m_cacheWriteBuffer;
Chris@297 288 m_cacheWriteBuffer = 0;
Chris@297 289
Chris@297 290 delete[] m_resampleBuffer;
Chris@297 291 m_resampleBuffer = 0;
Chris@297 292
Chris@297 293 delete m_resampler;
Chris@297 294 m_resampler = 0;
Chris@297 295
Chris@297 296 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 297 sf_close(m_cacheFileWritePtr);
Chris@297 298 m_cacheFileWritePtr = 0;
Chris@297 299 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297 300 }
Chris@297 301 }
Chris@297 302
Chris@297 303 void
Chris@1038 304 CodedAudioFileReader::pushBuffer(float *buffer, sv_frame_t sz, bool final)
Chris@297 305 {
Chris@757 306 m_fileFrameCount += sz;
Chris@757 307
Chris@1040 308 double ratio = 1.0;
Chris@758 309 if (m_resampler && m_fileRate != 0) {
Chris@1040 310 ratio = m_sampleRate / m_fileRate;
Chris@758 311 }
Chris@758 312
Chris@1040 313 if (ratio != 1.0) {
Chris@758 314 pushBufferResampling(buffer, sz, ratio, final);
Chris@758 315 } else {
Chris@758 316 pushBufferNonResampling(buffer, sz);
Chris@758 317 }
Chris@758 318 }
Chris@757 319
Chris@758 320 void
Chris@1038 321 CodedAudioFileReader::pushBufferNonResampling(float *buffer, sv_frame_t sz)
Chris@758 322 {
Chris@920 323 float clip = 1.0;
Chris@1038 324 sv_frame_t count = sz * m_channelCount;
Chris@318 325
Chris@920 326 if (m_normalised) {
Chris@1038 327 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 328 float v = fabsf(buffer[i]);
Chris@920 329 if (v > m_max) {
Chris@920 330 m_max = v;
Chris@920 331 m_gain = 1.f / m_max;
Chris@920 332 }
Chris@920 333 }
Chris@920 334 } else {
Chris@1038 335 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 336 if (buffer[i] > clip) buffer[i] = clip;
Chris@920 337 }
Chris@1038 338 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 339 if (buffer[i] < -clip) buffer[i] = -clip;
Chris@920 340 }
Chris@297 341 }
Chris@297 342
Chris@297 343 m_frameCount += sz;
Chris@297 344
Chris@148 345 switch (m_cacheMode) {
Chris@148 346
Chris@148 347 case CacheInTemporaryFile:
Chris@1038 348 if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
Chris@544 349 sf_close(m_cacheFileWritePtr);
Chris@544 350 m_cacheFileWritePtr = 0;
Chris@544 351 throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544 352 }
Chris@148 353 break;
Chris@148 354
Chris@148 355 case CacheInMemory:
Chris@543 356 m_dataLock.lockForWrite();
Chris@1096 357 m_data.insert(m_data.end(), buffer, buffer + count);
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@1096 411 vector<float>
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@1096 420 return {};
Chris@265 421 }
Chris@148 422
Chris@1096 423 vector<float> 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@1096 435 if (!isOK()) return {};
Chris@1096 436 if (count == 0) return {};
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