annotate data/fileio/CodedAudioFileReader.cpp @ 1177:79f7af8873c9

Add -ldl in a couple more places
author Chris Cannam
date Mon, 21 Mar 2016 10:02:21 +0000
parents 5b463c7727e5
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@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@1161 140
Chris@1161 141 // Previously we were writing SF_FORMAT_PCM_16 and in a
Chris@1161 142 // comment I wrote: "No point in writing 24-bit or float;
Chris@1161 143 // generally this class is used for decoding files that
Chris@1161 144 // have come from a 16 bit source or that decode to only
Chris@1161 145 // 16 bits anyway." That was naive -- we want to preserve
Chris@1161 146 // the original values to the same float precision that we
Chris@1161 147 // use internally. Saving PCM_16 obviously doesn't
Chris@1161 148 // preserve values for sources at bit depths greater than
Chris@1161 149 // 16, but it also doesn't always do so for sources at bit
Chris@1161 150 // depths less than 16.
Chris@1161 151 //
Chris@1161 152 // (This came to light with a bug in libsndfile 1.0.26,
Chris@1161 153 // which always reports every file as non-seekable, so
Chris@1161 154 // that coded readers were being used even for WAV
Chris@1161 155 // files. This changed the values that came from PCM_8 WAV
Chris@1161 156 // sources, breaking Sonic Annotator's output comparison
Chris@1161 157 // tests.)
Chris@1161 158 //
Chris@1161 159 // So: now we write floats.
Chris@1161 160 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
Chris@148 161
Chris@290 162 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 163 SFM_WRITE, &fileInfo);
Chris@148 164
Chris@265 165 if (m_cacheFileWritePtr) {
Chris@265 166
Chris@297 167 // Ideally we would do this now only if we were in a
Chris@297 168 // threaded mode -- creating the reader later if we're
Chris@297 169 // not threaded -- but we don't have access to that
Chris@297 170 // information here
Chris@265 171
Chris@265 172 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 173
Chris@265 174 if (!m_cacheFileReader->isOK()) {
Chris@843 175 cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
Chris@265 176 delete m_cacheFileReader;
Chris@265 177 m_cacheFileReader = 0;
Chris@265 178 m_cacheMode = CacheInMemory;
Chris@265 179 sf_close(m_cacheFileWritePtr);
Chris@265 180 }
Chris@297 181
Chris@265 182 } else {
Chris@843 183 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 184 m_cacheMode = CacheInMemory;
Chris@148 185 }
Chris@265 186
Chris@148 187 } catch (DirectoryCreationFailed f) {
Chris@843 188 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
Chris@148 189 m_cacheMode = CacheInMemory;
Chris@148 190 }
Chris@148 191 }
Chris@148 192
Chris@148 193 if (m_cacheMode == CacheInMemory) {
Chris@148 194 m_data.clear();
Chris@148 195 }
Chris@148 196
Chris@148 197 m_initialised = true;
Chris@148 198 }
Chris@148 199
Chris@148 200 void
Chris@1038 201 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
Chris@148 202 {
Chris@263 203 QMutexLocker locker(&m_cacheMutex);
Chris@263 204
Chris@148 205 if (!m_initialised) return;
Chris@148 206
Chris@1038 207 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 208
Chris@929 209 for (int c = 0; c < m_channelCount; ++c) {
Chris@148 210
Chris@297 211 float sample = samples[c][i];
Chris@297 212
Chris@297 213 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 214
Chris@297 215 if (m_cacheWriteBufferIndex ==
Chris@297 216 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 217
Chris@297 218 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 219 m_cacheWriteBufferIndex = 0;
Chris@297 220 }
Chris@297 221
Chris@297 222 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 223 m_cacheFileReader) {
Chris@297 224 m_cacheFileReader->updateFrameCount();
Chris@297 225 }
Chris@297 226 }
Chris@297 227 }
Chris@297 228 }
Chris@297 229
Chris@297 230 void
Chris@1038 231 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, sv_frame_t nframes)
Chris@297 232 {
Chris@297 233 QMutexLocker locker(&m_cacheMutex);
Chris@297 234
Chris@297 235 if (!m_initialised) return;
Chris@297 236
Chris@1038 237 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 238
Chris@929 239 for (int c = 0; c < m_channelCount; ++c) {
Chris@297 240
Chris@297 241 float sample = samples[i * m_channelCount + c];
Chris@297 242
Chris@297 243 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 244
Chris@297 245 if (m_cacheWriteBufferIndex ==
Chris@297 246 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 247
Chris@297 248 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 249 m_cacheWriteBufferIndex = 0;
Chris@297 250 }
Chris@297 251
Chris@297 252 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 253 m_cacheFileReader) {
Chris@297 254 m_cacheFileReader->updateFrameCount();
Chris@297 255 }
Chris@297 256 }
Chris@297 257 }
Chris@297 258 }
Chris@297 259
Chris@297 260 void
Chris@297 261 CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297 262 {
Chris@297 263 QMutexLocker locker(&m_cacheMutex);
Chris@297 264
Chris@297 265 if (!m_initialised) return;
Chris@297 266
Chris@1038 267 for (float sample: samples) {
Chris@297 268
Chris@148 269 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 270
Chris@148 271 if (m_cacheWriteBufferIndex ==
Chris@148 272 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 273
Chris@297 274 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 275 m_cacheWriteBufferIndex = 0;
Chris@266 276 }
Chris@265 277
Chris@266 278 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 279 m_cacheFileReader) {
Chris@266 280 m_cacheFileReader->updateFrameCount();
Chris@148 281 }
Chris@148 282 }
Chris@148 283 }
Chris@148 284
Chris@148 285 void
Chris@148 286 CodedAudioFileReader::finishDecodeCache()
Chris@148 287 {
Chris@263 288 QMutexLocker locker(&m_cacheMutex);
Chris@263 289
Chris@192 290 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 291
Chris@148 292 if (!m_initialised) {
Chris@843 293 cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
Chris@148 294 return;
Chris@148 295 }
Chris@148 296
Chris@920 297 pushBuffer(m_cacheWriteBuffer,
Chris@920 298 m_cacheWriteBufferIndex / m_channelCount,
Chris@920 299 true);
Chris@297 300
Chris@297 301 delete[] m_cacheWriteBuffer;
Chris@297 302 m_cacheWriteBuffer = 0;
Chris@297 303
Chris@297 304 delete[] m_resampleBuffer;
Chris@297 305 m_resampleBuffer = 0;
Chris@297 306
Chris@297 307 delete m_resampler;
Chris@297 308 m_resampler = 0;
Chris@297 309
Chris@297 310 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 311 sf_close(m_cacheFileWritePtr);
Chris@297 312 m_cacheFileWritePtr = 0;
Chris@297 313 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
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@543 370 m_dataLock.lockForWrite();
Chris@1038 371 for (sv_frame_t s = 0; s < count; ++s) {
Chris@543 372 m_data.push_back(buffer[s]);
Chris@297 373 }
Chris@543 374 m_dataLock.unlock();
Chris@148 375 break;
Chris@148 376 }
Chris@758 377 }
Chris@757 378
Chris@758 379 void
Chris@1038 380 CodedAudioFileReader::pushBufferResampling(float *buffer, sv_frame_t sz,
Chris@1038 381 double ratio, bool final)
Chris@758 382 {
Chris@759 383 SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
Chris@757 384
Chris@759 385 if (sz > 0) {
Chris@759 386
Chris@1038 387 sv_frame_t out = m_resampler->resampleInterleaved
Chris@759 388 (buffer,
Chris@759 389 m_resampleBuffer,
Chris@759 390 sz,
Chris@759 391 ratio,
Chris@759 392 false);
Chris@759 393
Chris@759 394 pushBufferNonResampling(m_resampleBuffer, out);
Chris@759 395 }
Chris@757 396
Chris@758 397 if (final) {
Chris@758 398
Chris@1038 399 sv_frame_t padFrames = 1;
Chris@1038 400 if (double(m_frameCount) / ratio < double(m_fileFrameCount)) {
Chris@1038 401 padFrames = m_fileFrameCount - sv_frame_t(double(m_frameCount) / ratio) + 1;
Chris@757 402 }
Chris@758 403
Chris@1038 404 sv_frame_t padSamples = padFrames * m_channelCount;
Chris@758 405
Chris@1038 406 SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
Chris@758 407
Chris@758 408 float *padding = new float[padSamples];
Chris@1038 409 for (sv_frame_t i = 0; i < padSamples; ++i) padding[i] = 0.f;
Chris@758 410
Chris@1038 411 sv_frame_t out = m_resampler->resampleInterleaved
Chris@758 412 (padding,
Chris@758 413 m_resampleBuffer,
Chris@758 414 padFrames,
Chris@758 415 ratio,
Chris@758 416 true);
Chris@758 417
Chris@1038 418 if (m_frameCount + out > sv_frame_t(double(m_fileFrameCount) * ratio)) {
Chris@1038 419 out = sv_frame_t(double(m_fileFrameCount) * ratio) - m_frameCount;
Chris@759 420 }
Chris@759 421
Chris@758 422 pushBufferNonResampling(m_resampleBuffer, out);
Chris@758 423 delete[] padding;
Chris@757 424 }
Chris@148 425 }
Chris@148 426
Chris@1041 427 SampleBlock
Chris@1041 428 CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 429 {
Chris@543 430 // Lock is only required in CacheInMemory mode (the cache file
Chris@543 431 // reader is expected to be thread safe and manage its own
Chris@543 432 // locking)
Chris@263 433
Chris@265 434 if (!m_initialised) {
Chris@690 435 SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
Chris@1041 436 return SampleBlock();
Chris@265 437 }
Chris@148 438
Chris@1041 439 SampleBlock frames;
Chris@1041 440
Chris@148 441 switch (m_cacheMode) {
Chris@148 442
Chris@148 443 case CacheInTemporaryFile:
Chris@148 444 if (m_cacheFileReader) {
Chris@1041 445 frames = m_cacheFileReader->getInterleavedFrames(start, count);
Chris@148 446 }
Chris@148 447 break;
Chris@148 448
Chris@148 449 case CacheInMemory:
Chris@148 450 {
Chris@1041 451 if (!isOK()) return SampleBlock();
Chris@1041 452 if (count == 0) return SampleBlock();
Chris@148 453
Chris@1038 454 sv_frame_t idx = start * m_channelCount;
Chris@1038 455 sv_frame_t i = 0;
Chris@1041 456 sv_frame_t n = count * m_channelCount;
Chris@148 457
Chris@1110 458 frames.resize(size_t(n));
Chris@1052 459
Chris@543 460 m_dataLock.lockForRead();
Chris@1041 461 while (i < n && in_range_for(m_data, idx)) {
Chris@1110 462 frames[size_t(i++)] = m_data[size_t(idx++)];
Chris@148 463 }
Chris@543 464 m_dataLock.unlock();
Chris@1052 465
Chris@1110 466 frames.resize(size_t(i));
Chris@148 467 }
Chris@148 468 }
Chris@920 469
Chris@920 470 if (m_normalised) {
Chris@1052 471 for (auto &f: frames) f *= m_gain;
Chris@920 472 }
Chris@1041 473
Chris@1041 474 return frames;
Chris@148 475 }
Chris@148 476