annotate data/fileio/CodedAudioFileReader.cpp @ 1008:d9e0e59a1581

When using an aggregate model to pass data to a transform, zero-pad the shorter input to the duration of the longer rather than truncating the longer. (This is better behaviour for e.g. MATCH, and in any case the code was previously truncating incorrectly and ending up with garbage data at the end.)
author Chris Cannam
date Fri, 14 Nov 2014 13:51:33 +0000
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