annotate data/fileio/CodedAudioFileReader.cpp @ 998:e25dc8d57565

Add descriptions for writers; add housekeeping options to Sonic Annotator to list writers and formats
author Chris Cannam
date Mon, 13 Oct 2014 14:44:51 +0100
parents d03b3d956358
children cc27f35aa75c
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@933 31 int 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@297 116 float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297 117 m_resampleBuffer = new float
Chris@398 118 [lrintf(ceilf(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@148 132 fileInfo.samplerate = m_sampleRate;
Chris@148 133 fileInfo.channels = m_channelCount;
Chris@297 134
Chris@297 135 // No point in writing 24-bit or float; generally this
Chris@297 136 // class is used for decoding files that have come from a
Chris@297 137 // 16 bit source or that decode to only 16 bits anyway.
Chris@297 138 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148 139
Chris@290 140 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 141 SFM_WRITE, &fileInfo);
Chris@148 142
Chris@265 143 if (m_cacheFileWritePtr) {
Chris@265 144
Chris@297 145 // Ideally we would do this now only if we were in a
Chris@297 146 // threaded mode -- creating the reader later if we're
Chris@297 147 // not threaded -- but we don't have access to that
Chris@297 148 // information here
Chris@265 149
Chris@265 150 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 151
Chris@265 152 if (!m_cacheFileReader->isOK()) {
Chris@843 153 cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
Chris@265 154 delete m_cacheFileReader;
Chris@265 155 m_cacheFileReader = 0;
Chris@265 156 m_cacheMode = CacheInMemory;
Chris@265 157 sf_close(m_cacheFileWritePtr);
Chris@265 158 }
Chris@297 159
Chris@265 160 } else {
Chris@843 161 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 162 m_cacheMode = CacheInMemory;
Chris@148 163 }
Chris@265 164
Chris@148 165 } catch (DirectoryCreationFailed f) {
Chris@843 166 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
Chris@148 167 m_cacheMode = CacheInMemory;
Chris@148 168 }
Chris@148 169 }
Chris@148 170
Chris@148 171 if (m_cacheMode == CacheInMemory) {
Chris@148 172 m_data.clear();
Chris@148 173 }
Chris@148 174
Chris@148 175 m_initialised = true;
Chris@148 176 }
Chris@148 177
Chris@148 178 void
Chris@929 179 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, int nframes)
Chris@148 180 {
Chris@263 181 QMutexLocker locker(&m_cacheMutex);
Chris@263 182
Chris@148 183 if (!m_initialised) return;
Chris@148 184
Chris@929 185 for (int i = 0; i < nframes; ++i) {
Chris@297 186
Chris@929 187 for (int c = 0; c < m_channelCount; ++c) {
Chris@148 188
Chris@297 189 float sample = samples[c][i];
Chris@297 190
Chris@297 191 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 192
Chris@297 193 if (m_cacheWriteBufferIndex ==
Chris@297 194 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 195
Chris@297 196 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 197 m_cacheWriteBufferIndex = 0;
Chris@297 198 }
Chris@297 199
Chris@297 200 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 201 m_cacheFileReader) {
Chris@297 202 m_cacheFileReader->updateFrameCount();
Chris@297 203 }
Chris@297 204 }
Chris@297 205 }
Chris@297 206 }
Chris@297 207
Chris@297 208 void
Chris@929 209 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, int nframes)
Chris@297 210 {
Chris@297 211 QMutexLocker locker(&m_cacheMutex);
Chris@297 212
Chris@297 213 if (!m_initialised) return;
Chris@297 214
Chris@929 215 for (int i = 0; i < nframes; ++i) {
Chris@297 216
Chris@929 217 for (int c = 0; c < m_channelCount; ++c) {
Chris@297 218
Chris@297 219 float sample = samples[i * m_channelCount + c];
Chris@297 220
Chris@297 221 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 222
Chris@297 223 if (m_cacheWriteBufferIndex ==
Chris@297 224 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 225
Chris@297 226 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 227 m_cacheWriteBufferIndex = 0;
Chris@297 228 }
Chris@297 229
Chris@297 230 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 231 m_cacheFileReader) {
Chris@297 232 m_cacheFileReader->updateFrameCount();
Chris@297 233 }
Chris@297 234 }
Chris@297 235 }
Chris@297 236 }
Chris@297 237
Chris@297 238 void
Chris@297 239 CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297 240 {
Chris@297 241 QMutexLocker locker(&m_cacheMutex);
Chris@297 242
Chris@297 243 if (!m_initialised) return;
Chris@297 244
Chris@929 245 for (int i = 0; i < (int)samples.size(); ++i) {
Chris@297 246
Chris@297 247 float sample = samples[i];
Chris@297 248
Chris@148 249 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 250
Chris@148 251 if (m_cacheWriteBufferIndex ==
Chris@148 252 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 253
Chris@297 254 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 255 m_cacheWriteBufferIndex = 0;
Chris@266 256 }
Chris@265 257
Chris@266 258 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 259 m_cacheFileReader) {
Chris@266 260 m_cacheFileReader->updateFrameCount();
Chris@148 261 }
Chris@148 262 }
Chris@148 263 }
Chris@148 264
Chris@148 265 void
Chris@148 266 CodedAudioFileReader::finishDecodeCache()
Chris@148 267 {
Chris@263 268 QMutexLocker locker(&m_cacheMutex);
Chris@263 269
Chris@192 270 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 271
Chris@148 272 if (!m_initialised) {
Chris@843 273 cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
Chris@148 274 return;
Chris@148 275 }
Chris@148 276
Chris@920 277 pushBuffer(m_cacheWriteBuffer,
Chris@920 278 m_cacheWriteBufferIndex / m_channelCount,
Chris@920 279 true);
Chris@297 280
Chris@297 281 delete[] m_cacheWriteBuffer;
Chris@297 282 m_cacheWriteBuffer = 0;
Chris@297 283
Chris@297 284 delete[] m_resampleBuffer;
Chris@297 285 m_resampleBuffer = 0;
Chris@297 286
Chris@297 287 delete m_resampler;
Chris@297 288 m_resampler = 0;
Chris@297 289
Chris@297 290 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 291 sf_close(m_cacheFileWritePtr);
Chris@297 292 m_cacheFileWritePtr = 0;
Chris@297 293 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297 294 }
Chris@297 295 }
Chris@297 296
Chris@297 297 void
Chris@929 298 CodedAudioFileReader::pushBuffer(float *buffer, int sz, bool final)
Chris@297 299 {
Chris@757 300 m_fileFrameCount += sz;
Chris@757 301
Chris@757 302 float ratio = 1.f;
Chris@758 303 if (m_resampler && m_fileRate != 0) {
Chris@758 304 ratio = float(m_sampleRate) / float(m_fileRate);
Chris@758 305 }
Chris@758 306
Chris@758 307 if (ratio != 1.f) {
Chris@758 308 pushBufferResampling(buffer, sz, ratio, final);
Chris@758 309 } else {
Chris@758 310 pushBufferNonResampling(buffer, sz);
Chris@758 311 }
Chris@758 312 }
Chris@757 313
Chris@758 314 void
Chris@929 315 CodedAudioFileReader::pushBufferNonResampling(float *buffer, int sz)
Chris@758 316 {
Chris@920 317 float clip = 1.0;
Chris@929 318 int count = sz * m_channelCount;
Chris@318 319
Chris@920 320 if (m_normalised) {
Chris@933 321 for (int i = 0; i < count; ++i) {
Chris@920 322 float v = fabsf(buffer[i]);
Chris@920 323 if (v > m_max) {
Chris@920 324 m_max = v;
Chris@920 325 m_gain = 1.f / m_max;
Chris@920 326 }
Chris@920 327 }
Chris@920 328 } else {
Chris@933 329 for (int i = 0; i < count; ++i) {
Chris@920 330 if (buffer[i] > clip) buffer[i] = clip;
Chris@920 331 }
Chris@933 332 for (int i = 0; i < count; ++i) {
Chris@920 333 if (buffer[i] < -clip) buffer[i] = -clip;
Chris@920 334 }
Chris@297 335 }
Chris@297 336
Chris@297 337 m_frameCount += sz;
Chris@297 338
Chris@148 339 switch (m_cacheMode) {
Chris@148 340
Chris@148 341 case CacheInTemporaryFile:
Chris@929 342 if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < (int)sz) {
Chris@544 343 sf_close(m_cacheFileWritePtr);
Chris@544 344 m_cacheFileWritePtr = 0;
Chris@544 345 throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544 346 }
Chris@148 347 break;
Chris@148 348
Chris@148 349 case CacheInMemory:
Chris@543 350 m_dataLock.lockForWrite();
Chris@929 351 for (int s = 0; s < count; ++s) {
Chris@543 352 m_data.push_back(buffer[s]);
Chris@297 353 }
Chris@297 354 MUNLOCK_SAMPLEBLOCK(m_data);
Chris@543 355 m_dataLock.unlock();
Chris@148 356 break;
Chris@148 357 }
Chris@758 358 }
Chris@757 359
Chris@758 360 void
Chris@929 361 CodedAudioFileReader::pushBufferResampling(float *buffer, int sz,
Chris@758 362 float ratio, bool final)
Chris@758 363 {
Chris@759 364 SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
Chris@757 365
Chris@759 366 if (sz > 0) {
Chris@759 367
Chris@929 368 int out = m_resampler->resampleInterleaved
Chris@759 369 (buffer,
Chris@759 370 m_resampleBuffer,
Chris@759 371 sz,
Chris@759 372 ratio,
Chris@759 373 false);
Chris@759 374
Chris@759 375 pushBufferNonResampling(m_resampleBuffer, out);
Chris@759 376 }
Chris@757 377
Chris@758 378 if (final) {
Chris@758 379
Chris@929 380 int padFrames = 1;
Chris@758 381 if (m_frameCount / ratio < m_fileFrameCount) {
Chris@758 382 padFrames = m_fileFrameCount - (m_frameCount / ratio) + 1;
Chris@757 383 }
Chris@758 384
Chris@929 385 int padSamples = padFrames * m_channelCount;
Chris@758 386
Chris@759 387 SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << m_frameCount / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
Chris@758 388
Chris@758 389 float *padding = new float[padSamples];
Chris@758 390 for (int i = 0; i < padSamples; ++i) padding[i] = 0.f;
Chris@758 391
Chris@929 392 int out = m_resampler->resampleInterleaved
Chris@758 393 (padding,
Chris@758 394 m_resampleBuffer,
Chris@758 395 padFrames,
Chris@758 396 ratio,
Chris@758 397 true);
Chris@758 398
Chris@929 399 if (int(m_frameCount + out) > int(m_fileFrameCount * ratio)) {
Chris@929 400 out = int(m_fileFrameCount * ratio) - int(m_frameCount);
Chris@759 401 }
Chris@759 402
Chris@758 403 pushBufferNonResampling(m_resampleBuffer, out);
Chris@758 404 delete[] padding;
Chris@757 405 }
Chris@148 406 }
Chris@148 407
Chris@148 408 void
Chris@929 409 CodedAudioFileReader::getInterleavedFrames(int start, int count,
Chris@148 410 SampleBlock &frames) const
Chris@148 411 {
Chris@543 412 // Lock is only required in CacheInMemory mode (the cache file
Chris@543 413 // reader is expected to be thread safe and manage its own
Chris@543 414 // locking)
Chris@263 415
Chris@265 416 if (!m_initialised) {
Chris@690 417 SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
Chris@265 418 return;
Chris@265 419 }
Chris@148 420
Chris@148 421 switch (m_cacheMode) {
Chris@148 422
Chris@148 423 case CacheInTemporaryFile:
Chris@148 424 if (m_cacheFileReader) {
Chris@148 425 m_cacheFileReader->getInterleavedFrames(start, count, frames);
Chris@148 426 }
Chris@148 427 break;
Chris@148 428
Chris@148 429 case CacheInMemory:
Chris@148 430 {
Chris@148 431 frames.clear();
Chris@148 432 if (!isOK()) return;
Chris@148 433 if (count == 0) return;
Chris@543 434 frames.reserve(count * m_channelCount);
Chris@148 435
Chris@929 436 int idx = start * m_channelCount;
Chris@929 437 int i = 0;
Chris@148 438
Chris@543 439 m_dataLock.lockForRead();
Chris@929 440 while (i < count * m_channelCount && idx < (int)m_data.size()) {
Chris@543 441 frames.push_back(m_data[idx]);
Chris@543 442 ++idx;
Chris@148 443 }
Chris@543 444 m_dataLock.unlock();
Chris@148 445 }
Chris@148 446 }
Chris@920 447
Chris@920 448 if (m_normalised) {
Chris@920 449 for (int i = 0; i < (int)(count * m_channelCount); ++i) {
Chris@920 450 frames[i] *= m_gain;
Chris@920 451 }
Chris@920 452 }
Chris@148 453 }
Chris@148 454