annotate data/fileio/CodedAudioFileReader.cpp @ 758:babed5be1ae7

Better general approach
author Chris Cannam
date Fri, 08 Mar 2013 20:05:49 +0000
parents b98f5daab19e
children a43acbe3988f
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@297 31 size_t targetRate) :
Chris@148 32 m_cacheMode(cacheMode),
Chris@148 33 m_initialised(false),
Chris@297 34 m_serialiser(0),
Chris@297 35 m_fileRate(0),
Chris@148 36 m_cacheFileWritePtr(0),
Chris@148 37 m_cacheFileReader(0),
Chris@148 38 m_cacheWriteBuffer(0),
Chris@148 39 m_cacheWriteBufferIndex(0),
Chris@297 40 m_cacheWriteBufferSize(16384),
Chris@297 41 m_resampler(0),
Chris@757 42 m_resampleBuffer(0),
Chris@757 43 m_fileFrameCount(0)
Chris@148 44 {
Chris@742 45 SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << endl;
Chris@297 46
Chris@297 47 m_frameCount = 0;
Chris@297 48 m_sampleRate = targetRate;
Chris@148 49 }
Chris@148 50
Chris@148 51 CodedAudioFileReader::~CodedAudioFileReader()
Chris@148 52 {
Chris@263 53 QMutexLocker locker(&m_cacheMutex);
Chris@263 54
Chris@297 55 endSerialised();
Chris@297 56
Chris@148 57 if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
Chris@297 58
Chris@742 59 SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
Chris@532 60
Chris@297 61 delete m_cacheFileReader;
Chris@297 62 delete[] m_cacheWriteBuffer;
Chris@148 63
Chris@148 64 if (m_cacheFileName != "") {
Chris@290 65 if (!QFile(m_cacheFileName).remove()) {
Chris@686 66 std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << std::endl;
Chris@148 67 }
Chris@148 68 }
Chris@297 69
Chris@297 70 delete m_resampler;
Chris@297 71 delete[] m_resampleBuffer;
Chris@297 72 }
Chris@297 73
Chris@297 74 void
Chris@297 75 CodedAudioFileReader::startSerialised(QString id)
Chris@297 76 {
Chris@742 77 SVDEBUG << "CodedAudioFileReader::startSerialised(" << id << ")" << endl;
Chris@297 78
Chris@297 79 delete m_serialiser;
Chris@297 80 m_serialiser = new Serialiser(id);
Chris@297 81 }
Chris@297 82
Chris@297 83 void
Chris@297 84 CodedAudioFileReader::endSerialised()
Chris@297 85 {
Chris@756 86 SVDEBUG << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId().toStdString() : "(none)") << endl;
Chris@297 87
Chris@297 88 delete m_serialiser;
Chris@297 89 m_serialiser = 0;
Chris@148 90 }
Chris@148 91
Chris@148 92 void
Chris@148 93 CodedAudioFileReader::initialiseDecodeCache()
Chris@148 94 {
Chris@263 95 QMutexLocker locker(&m_cacheMutex);
Chris@263 96
Chris@742 97 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl;
Chris@297 98
Chris@297 99 if (m_fileRate == 0) {
Chris@754 100 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << std::endl;
Chris@754 101 throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)");
Chris@297 102 }
Chris@297 103 if (m_sampleRate == 0) {
Chris@297 104 m_sampleRate = m_fileRate;
Chris@690 105 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << endl;
Chris@297 106 }
Chris@297 107 if (m_fileRate != m_sampleRate) {
Chris@757 108 SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << endl;
Chris@297 109 m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297 110 m_channelCount,
Chris@297 111 m_cacheWriteBufferSize);
Chris@297 112 float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297 113 m_resampleBuffer = new float
Chris@398 114 [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio + 1))];
Chris@297 115 }
Chris@297 116
Chris@297 117 m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297 118 m_cacheWriteBufferIndex = 0;
Chris@297 119
Chris@148 120 if (m_cacheMode == CacheInTemporaryFile) {
Chris@148 121
Chris@148 122 try {
Chris@148 123 QDir dir(TempDirectory::getInstance()->getPath());
Chris@148 124 m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290 125 .arg((intptr_t)this));
Chris@148 126
Chris@148 127 SF_INFO fileInfo;
Chris@148 128 fileInfo.samplerate = m_sampleRate;
Chris@148 129 fileInfo.channels = m_channelCount;
Chris@297 130
Chris@297 131 // No point in writing 24-bit or float; generally this
Chris@297 132 // class is used for decoding files that have come from a
Chris@297 133 // 16 bit source or that decode to only 16 bits anyway.
Chris@297 134 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148 135
Chris@290 136 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 137 SFM_WRITE, &fileInfo);
Chris@148 138
Chris@265 139 if (m_cacheFileWritePtr) {
Chris@265 140
Chris@297 141 // Ideally we would do this now only if we were in a
Chris@297 142 // threaded mode -- creating the reader later if we're
Chris@297 143 // not threaded -- but we don't have access to that
Chris@297 144 // information here
Chris@265 145
Chris@265 146 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 147
Chris@265 148 if (!m_cacheFileReader->isOK()) {
Chris@686 149 std::cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << std::endl;
Chris@265 150 delete m_cacheFileReader;
Chris@265 151 m_cacheFileReader = 0;
Chris@265 152 m_cacheMode = CacheInMemory;
Chris@265 153 sf_close(m_cacheFileWritePtr);
Chris@265 154 }
Chris@297 155
Chris@265 156 } else {
Chris@686 157 std::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" << std::endl;
Chris@148 158 m_cacheMode = CacheInMemory;
Chris@148 159 }
Chris@265 160
Chris@148 161 } catch (DirectoryCreationFailed f) {
Chris@148 162 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << std::endl;
Chris@148 163 m_cacheMode = CacheInMemory;
Chris@148 164 }
Chris@148 165 }
Chris@148 166
Chris@148 167 if (m_cacheMode == CacheInMemory) {
Chris@148 168 m_data.clear();
Chris@148 169 }
Chris@148 170
Chris@148 171 m_initialised = true;
Chris@148 172 }
Chris@148 173
Chris@148 174 void
Chris@297 175 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, size_t nframes)
Chris@148 176 {
Chris@263 177 QMutexLocker locker(&m_cacheMutex);
Chris@263 178
Chris@148 179 if (!m_initialised) return;
Chris@148 180
Chris@297 181 for (size_t i = 0; i < nframes; ++i) {
Chris@297 182
Chris@297 183 for (size_t c = 0; c < m_channelCount; ++c) {
Chris@148 184
Chris@297 185 float sample = samples[c][i];
Chris@297 186
Chris@297 187 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 188
Chris@297 189 if (m_cacheWriteBufferIndex ==
Chris@297 190 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 191
Chris@297 192 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 193 m_cacheWriteBufferIndex = 0;
Chris@297 194 }
Chris@297 195
Chris@297 196 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 197 m_cacheFileReader) {
Chris@297 198 m_cacheFileReader->updateFrameCount();
Chris@297 199 }
Chris@297 200 }
Chris@297 201 }
Chris@297 202 }
Chris@297 203
Chris@297 204 void
Chris@297 205 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, size_t nframes)
Chris@297 206 {
Chris@297 207 QMutexLocker locker(&m_cacheMutex);
Chris@297 208
Chris@297 209 if (!m_initialised) return;
Chris@297 210
Chris@297 211 for (size_t i = 0; i < nframes; ++i) {
Chris@297 212
Chris@297 213 for (size_t c = 0; c < m_channelCount; ++c) {
Chris@297 214
Chris@297 215 float sample = samples[i * m_channelCount + c];
Chris@297 216
Chris@297 217 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 218
Chris@297 219 if (m_cacheWriteBufferIndex ==
Chris@297 220 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 221
Chris@297 222 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 223 m_cacheWriteBufferIndex = 0;
Chris@297 224 }
Chris@297 225
Chris@297 226 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 227 m_cacheFileReader) {
Chris@297 228 m_cacheFileReader->updateFrameCount();
Chris@297 229 }
Chris@297 230 }
Chris@297 231 }
Chris@297 232 }
Chris@297 233
Chris@297 234 void
Chris@297 235 CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297 236 {
Chris@297 237 QMutexLocker locker(&m_cacheMutex);
Chris@297 238
Chris@297 239 if (!m_initialised) return;
Chris@297 240
Chris@297 241 for (size_t i = 0; i < samples.size(); ++i) {
Chris@297 242
Chris@297 243 float sample = samples[i];
Chris@297 244
Chris@148 245 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 246
Chris@148 247 if (m_cacheWriteBufferIndex ==
Chris@148 248 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 249
Chris@297 250 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 251 m_cacheWriteBufferIndex = 0;
Chris@266 252 }
Chris@265 253
Chris@266 254 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 255 m_cacheFileReader) {
Chris@266 256 m_cacheFileReader->updateFrameCount();
Chris@148 257 }
Chris@148 258 }
Chris@148 259 }
Chris@148 260
Chris@148 261 void
Chris@148 262 CodedAudioFileReader::finishDecodeCache()
Chris@148 263 {
Chris@263 264 QMutexLocker locker(&m_cacheMutex);
Chris@263 265
Chris@192 266 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 267
Chris@148 268 if (!m_initialised) {
Chris@148 269 std::cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << std::endl;
Chris@148 270 return;
Chris@148 271 }
Chris@148 272
Chris@297 273 if (m_cacheWriteBufferIndex > 0) {
Chris@297 274 pushBuffer(m_cacheWriteBuffer,
Chris@297 275 m_cacheWriteBufferIndex / m_channelCount,
Chris@297 276 true);
Chris@297 277 }
Chris@297 278
Chris@297 279 delete[] m_cacheWriteBuffer;
Chris@297 280 m_cacheWriteBuffer = 0;
Chris@297 281
Chris@297 282 delete[] m_resampleBuffer;
Chris@297 283 m_resampleBuffer = 0;
Chris@297 284
Chris@297 285 delete m_resampler;
Chris@297 286 m_resampler = 0;
Chris@297 287
Chris@297 288 if (m_cacheMode == CacheInTemporaryFile) {
Chris@297 289 sf_close(m_cacheFileWritePtr);
Chris@297 290 m_cacheFileWritePtr = 0;
Chris@297 291 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297 292 }
Chris@297 293 }
Chris@297 294
Chris@297 295 void
Chris@297 296 CodedAudioFileReader::pushBuffer(float *buffer, size_t sz, bool final)
Chris@297 297 {
Chris@757 298 m_fileFrameCount += sz;
Chris@757 299
Chris@757 300 float ratio = 1.f;
Chris@758 301 if (m_resampler && m_fileRate != 0) {
Chris@758 302 ratio = float(m_sampleRate) / float(m_fileRate);
Chris@758 303 }
Chris@758 304
Chris@758 305 if (ratio != 1.f) {
Chris@758 306 pushBufferResampling(buffer, sz, ratio, final);
Chris@758 307 } else {
Chris@758 308 pushBufferNonResampling(buffer, sz);
Chris@758 309 }
Chris@758 310 }
Chris@757 311
Chris@758 312 void
Chris@758 313 CodedAudioFileReader::pushBufferNonResampling(float *buffer, size_t sz)
Chris@758 314 {
Chris@758 315 float max = 1.0;
Chris@758 316 size_t count = sz * m_channelCount;
Chris@318 317
Chris@322 318 for (size_t i = 0; i < count; ++i) {
Chris@322 319 if (buffer[i] > max) buffer[i] = max;
Chris@322 320 }
Chris@322 321 for (size_t i = 0; i < count; ++i) {
Chris@322 322 if (buffer[i] < -max) buffer[i] = -max;
Chris@297 323 }
Chris@297 324
Chris@297 325 m_frameCount += sz;
Chris@297 326
Chris@148 327 switch (m_cacheMode) {
Chris@148 328
Chris@148 329 case CacheInTemporaryFile:
Chris@544 330 if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
Chris@544 331 sf_close(m_cacheFileWritePtr);
Chris@544 332 m_cacheFileWritePtr = 0;
Chris@544 333 throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544 334 }
Chris@148 335 break;
Chris@148 336
Chris@148 337 case CacheInMemory:
Chris@543 338 m_dataLock.lockForWrite();
Chris@322 339 for (size_t s = 0; s < count; ++s) {
Chris@543 340 m_data.push_back(buffer[s]);
Chris@297 341 }
Chris@297 342 MUNLOCK_SAMPLEBLOCK(m_data);
Chris@543 343 m_dataLock.unlock();
Chris@148 344 break;
Chris@148 345 }
Chris@758 346 }
Chris@757 347
Chris@758 348 void
Chris@758 349 CodedAudioFileReader::pushBufferResampling(float *buffer, size_t sz,
Chris@758 350 float ratio, bool final)
Chris@758 351 {
Chris@758 352 size_t out = m_resampler->resampleInterleaved
Chris@758 353 (buffer,
Chris@758 354 m_resampleBuffer,
Chris@758 355 sz,
Chris@758 356 ratio,
Chris@758 357 false);
Chris@757 358
Chris@758 359 pushBufferNonResampling(m_resampleBuffer, out);
Chris@757 360
Chris@758 361 if (final) {
Chris@758 362
Chris@758 363 size_t padFrames = 1;
Chris@758 364 if (m_frameCount / ratio < m_fileFrameCount) {
Chris@758 365 padFrames = m_fileFrameCount - (m_frameCount / ratio) + 1;
Chris@757 366 }
Chris@758 367
Chris@758 368 size_t padSamples = padFrames * m_channelCount;
Chris@758 369
Chris@758 370 std::cerr << "frameCount = " << m_frameCount << ", equivFileFrames = " << m_frameCount / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << std::endl;
Chris@758 371
Chris@758 372 float *padding = new float[padSamples];
Chris@758 373 for (int i = 0; i < padSamples; ++i) padding[i] = 0.f;
Chris@758 374
Chris@758 375 out = m_resampler->resampleInterleaved
Chris@758 376 (padding,
Chris@758 377 m_resampleBuffer,
Chris@758 378 padFrames,
Chris@758 379 ratio,
Chris@758 380 true);
Chris@758 381
Chris@758 382 pushBufferNonResampling(m_resampleBuffer, out);
Chris@758 383 delete[] padding;
Chris@757 384 }
Chris@148 385 }
Chris@148 386
Chris@148 387 void
Chris@148 388 CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count,
Chris@148 389 SampleBlock &frames) const
Chris@148 390 {
Chris@543 391 // Lock is only required in CacheInMemory mode (the cache file
Chris@543 392 // reader is expected to be thread safe and manage its own
Chris@543 393 // locking)
Chris@263 394
Chris@265 395 if (!m_initialised) {
Chris@690 396 SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
Chris@265 397 return;
Chris@265 398 }
Chris@148 399
Chris@148 400 switch (m_cacheMode) {
Chris@148 401
Chris@148 402 case CacheInTemporaryFile:
Chris@148 403 if (m_cacheFileReader) {
Chris@148 404 m_cacheFileReader->getInterleavedFrames(start, count, frames);
Chris@148 405 }
Chris@148 406 break;
Chris@148 407
Chris@148 408 case CacheInMemory:
Chris@148 409 {
Chris@148 410 frames.clear();
Chris@148 411 if (!isOK()) return;
Chris@148 412 if (count == 0) return;
Chris@543 413 frames.reserve(count * m_channelCount);
Chris@148 414
Chris@543 415 size_t idx = start * m_channelCount;
Chris@543 416 size_t i = 0;
Chris@148 417
Chris@543 418 m_dataLock.lockForRead();
Chris@543 419 while (i < count * m_channelCount && idx < m_data.size()) {
Chris@543 420 frames.push_back(m_data[idx]);
Chris@543 421 ++idx;
Chris@148 422 }
Chris@543 423 m_dataLock.unlock();
Chris@148 424 }
Chris@148 425 }
Chris@148 426 }
Chris@148 427