CodedAudioFileReader.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006-2007 Chris Cannam and QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "CodedAudioFileReader.h"
17 
18 #include "WavFileReader.h"
19 #include "base/TempDirectory.h"
20 #include "base/Exceptions.h"
21 #include "base/Profiler.h"
22 #include "base/Serialiser.h"
23 #include "base/StorageAdviser.h"
24 
25 #include <bqresample/Resampler.h>
26 
27 #include <stdint.h>
28 #include <iostream>
29 #include <QDir>
30 #include <QMutexLocker>
31 
32 using namespace std;
33 
35  sv_samplerate_t targetRate,
36  bool normalised) :
37  m_cacheMode(cacheMode),
38  m_initialised(false),
39  m_serialiser(nullptr),
40  m_fileRate(0),
41  m_cacheFileWritePtr(nullptr),
42  m_cacheFileReader(nullptr),
43  m_cacheWriteBuffer(nullptr),
44  m_cacheWriteBufferIndex(0),
45  m_cacheWriteBufferFrames(65536),
46  m_resampler(nullptr),
47  m_resampleBuffer(nullptr),
48  m_resampleBufferFrames(0),
49  m_fileFrameCount(0),
50  m_normalised(normalised),
51  m_max(0.f),
52  m_gain(1.f),
53  m_trimFromStart(0),
54  m_trimFromEnd(0),
55  m_clippedCount(0),
56  m_firstNonzero(0),
57  m_lastNonzero(0)
58 {
59  SVDEBUG << "CodedAudioFileReader:: cache mode: " << cacheMode
60  << " (" << (cacheMode == CacheInTemporaryFile
61  ? "CacheInTemporaryFile" : "CacheInMemory") << ")"
62  << ", rate: " << targetRate
63  << (targetRate == 0 ? " (use source rate)" : "")
64  << ", normalised: " << normalised << endl;
65 
66  m_frameCount = 0;
67  m_sampleRate = targetRate;
68 }
69 
71 {
72  QMutexLocker locker(&m_cacheMutex);
73 
75 
77 
78  SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
79 
80  delete m_cacheFileReader;
81  delete[] m_cacheWriteBuffer;
82 
83  if (m_cacheFileName != "") {
84  SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file " << m_cacheFileName << endl;
85  if (!QFile(m_cacheFileName).remove()) {
86  SVDEBUG << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl;
87  }
88  }
89 
90  delete m_resampler;
91  delete[] m_resampleBuffer;
92 
93  if (!m_data.empty()) {
96  (m_data.size() * sizeof(float)) / 1024);
97  }
98 }
99 
100 void
102 {
103  m_trimFromStart = fromStart;
104  m_trimFromEnd = fromEnd;
105 }
106 
107 void
109  const std::atomic<bool> *cancelled)
110 {
111 // SVCERR << "CodedAudioFileReader(" << this << ")::startSerialised: id = " << id << endl;
112 
113  delete m_serialiser;
114  m_serialiser = new Serialiser(id, cancelled);
115 }
116 
117 void
119 {
120 // SVCERR << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId() : "(none)") << endl;
121 
122  delete m_serialiser;
123  m_serialiser = nullptr;
124 }
125 
126 void
128 {
129  QMutexLocker locker(&m_cacheMutex);
130 
131  SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl;
132 
133  if (m_channelCount == 0) {
134  SVCERR << "CodedAudioFileReader::initialiseDecodeCache: No channel count set!" << endl;
135  throw std::logic_error("No channel count set");
136  }
137 
138  if (m_fileRate == 0) {
139  SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl;
140  throw FileOperationFailed("(coded file)", "sample rate unknown (bug in subclass implementation?)");
141  }
142  if (m_sampleRate == 0) {
144  SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << endl;
145  }
146  if (m_fileRate != m_sampleRate) {
147  SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << endl;
148 
149  breakfastquay::Resampler::Parameters params;
150  params.quality = breakfastquay::Resampler::FastestTolerable;
151  params.maxBufferSize = int(m_cacheWriteBufferFrames);
152  params.initialSampleRate = m_fileRate;
153  m_resampler = new breakfastquay::Resampler(params, m_channelCount);
154 
155  double ratio = m_sampleRate / m_fileRate;
157  ratio + 1));
159  }
160 
163 
165 
166  try {
167  QDir dir(TempDirectory::getInstance()->getPath());
168  m_cacheFileName = dir.filePath(QString("decoded_%1.w64")
169  .arg((intptr_t)this));
170 
171  SF_INFO fileInfo;
172  int fileRate = int(round(m_sampleRate));
173  if (m_sampleRate != sv_samplerate_t(fileRate)) {
174  SVDEBUG << "CodedAudioFileReader: WARNING: Non-integer sample rate "
175  << m_sampleRate << " presented for writing, rounding to " << fileRate
176  << endl;
177  }
178  fileInfo.samplerate = fileRate;
179  fileInfo.channels = m_channelCount;
180 
181  // Previously we were writing SF_FORMAT_PCM_16 and in a
182  // comment I wrote: "No point in writing 24-bit or float;
183  // generally this class is used for decoding files that
184  // have come from a 16 bit source or that decode to only
185  // 16 bits anyway." That was naive -- we want to preserve
186  // the original values to the same float precision that we
187  // use internally. Saving PCM_16 obviously doesn't
188  // preserve values for sources at bit depths greater than
189  // 16, but it also doesn't always do so for sources at bit
190  // depths less than 16.
191  //
192  // (This came to light with a bug in libsndfile 1.0.26,
193  // which always reports every file as non-seekable, so
194  // that coded readers were being used even for WAV
195  // files. This changed the values that came from PCM_8 WAV
196  // sources, breaking Sonic Annotator's output comparison
197  // tests.)
198  //
199  // So: now we write floats.
200  fileInfo.format = SF_FORMAT_W64 | SF_FORMAT_FLOAT;
201 
202 #ifdef Q_OS_WIN
203  m_cacheFileWritePtr = sf_wchar_open
204  ((LPCWSTR)m_cacheFileName.utf16(), SFM_WRITE, &fileInfo);
205 #else
206  m_cacheFileWritePtr = sf_open
207  (m_cacheFileName.toLocal8Bit(), SFM_WRITE, &fileInfo);
208 #endif
209 
210  if (m_cacheFileWritePtr) {
211 
212  // Ideally we would do this now only if we were in a
213  // threaded mode -- creating the reader later if we're
214  // not threaded -- but we don't have access to that
215  // information here
216 
218 
219  if (!m_cacheFileReader->isOK()) {
220  SVDEBUG << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
221  delete m_cacheFileReader;
222  m_cacheFileReader = nullptr;
224  sf_close(m_cacheFileWritePtr);
225  }
226 
227  } else {
228  SVDEBUG << "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;
230  }
231 
232  } catch (const DirectoryCreationFailed &f) {
233  SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
235  }
236  }
237 
238  if (m_cacheMode == CacheInMemory) {
239  m_data.clear();
240  }
241 
243  SVCERR << "WARNING: CodedAudioFileReader::setSamplesToTrim: Can't handle trimming more frames from end (" << m_trimFromEnd << ") than can be stored in cache-write buffer (" << (m_cacheWriteBufferFrames * m_channelCount) << "), won't trim anything from the end after all";
244  m_trimFromEnd = 0;
245  }
246 
247  m_initialised = true;
248 }
249 
250 void
252 {
253  QMutexLocker locker(&m_cacheMutex);
254 
255  if (!m_initialised) return;
256 
257  for (sv_frame_t i = 0; i < nframes; ++i) {
258 
259  if (m_trimFromStart > 0) {
260  --m_trimFromStart;
261  continue;
262  }
263 
264  for (int c = 0; c < m_channelCount; ++c) {
265 
266  float sample = samples[c][i];
268 
269  }
270 
272  }
273 }
274 
275 void
277 {
278  QMutexLocker locker(&m_cacheMutex);
279 
280  if (!m_initialised) return;
281 
282  for (sv_frame_t i = 0; i < nframes; ++i) {
283 
284  if (m_trimFromStart > 0) {
285  --m_trimFromStart;
286  continue;
287  }
288 
289  for (int c = 0; c < m_channelCount; ++c) {
290 
291  float sample = samples[i * m_channelCount + c];
292 
294  }
295 
297  }
298 }
299 
300 void
302 {
303  QMutexLocker locker(&m_cacheMutex);
304 
305  if (!m_initialised) return;
306 
307  for (float sample: samples) {
308 
309  if (m_trimFromStart > 0) {
310  --m_trimFromStart;
311  continue;
312  }
313 
315 
317  }
318 }
319 
320 void
322 {
323  QMutexLocker locker(&m_cacheMutex);
324 
325  Profiler profiler("CodedAudioFileReader::finishDecodeCache");
326 
327  if (!m_initialised) {
328  SVDEBUG << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
329  return;
330  }
331 
333 
334  delete[] m_cacheWriteBuffer;
335  m_cacheWriteBuffer = nullptr;
336 
337  delete[] m_resampleBuffer;
338  m_resampleBuffer = nullptr;
339 
340  delete m_resampler;
341  m_resampler = nullptr;
342 
344 
345  sf_close(m_cacheFileWritePtr);
346  m_cacheFileWritePtr = nullptr;
348 
349  } else {
350  // I know, I know, we already allocated it...
353  (m_data.size() * sizeof(float)) / 1024);
354  }
355 
356  SVDEBUG << "CodedAudioFileReader: File decodes to " << m_fileFrameCount
357  << " frames" << endl;
359  SVDEBUG << "CodedAudioFileReader: Resampled to " << m_frameCount
360  << " frames" << endl;
361  }
362  SVDEBUG << "CodedAudioFileReader: Signal abs max is " << m_max
363  << ", " << m_clippedCount
364  << " samples clipped, first non-zero frame is at "
365  << m_firstNonzero << ", last at " << m_lastNonzero << endl;
366  if (m_normalised) {
367  SVDEBUG << "CodedAudioFileReader: Normalising, gain is " << m_gain << endl;
368  }
369 }
370 
371 void
373 {
374  if (final ||
377 
378  if (m_trimFromEnd > 0) {
379 
380  sv_frame_t framesToPush =
382 
383  if (framesToPush <= 0 && !final) {
384  // This won't do, the buffer is full so we have to push
385  // something. Should have checked for this earlier
386  throw std::logic_error("Buffer full but nothing to push");
387  }
388 
389  pushBuffer(m_cacheWriteBuffer, framesToPush, final);
390 
391  m_cacheWriteBufferIndex -= framesToPush * m_channelCount;
392 
393  for (sv_frame_t i = 0; i < m_cacheWriteBufferIndex; ++i) {
394  m_cacheWriteBuffer[i] =
395  m_cacheWriteBuffer[framesToPush * m_channelCount + i];
396  }
397 
398  } else {
399 
401  m_cacheWriteBufferIndex / m_channelCount,
402  final);
403 
405  }
406 
407  if (m_cacheFileReader) {
409  }
410  }
411 }
412 
414 CodedAudioFileReader::pushBuffer(float *buffer, sv_frame_t sz, bool final)
415 {
416  m_fileFrameCount += sz;
417 
418  double ratio = 1.0;
419  if (m_resampler && m_fileRate != 0) {
420  ratio = m_sampleRate / m_fileRate;
421  }
422 
423  if (ratio != 1.0) {
424  pushBufferResampling(buffer, sz, ratio, final);
425  } else {
426  pushBufferNonResampling(buffer, sz);
427  }
428 
429  return sz;
430 }
431 
432 void
434 {
435  float clip = 1.0;
436  sv_frame_t count = sz * m_channelCount;
437 
438  // statistics
439  for (sv_frame_t j = 0; j < sz; ++j) {
440  for (int c = 0; c < m_channelCount; ++c) {
441  sv_frame_t i = j * m_channelCount + c;
442  float v = buffer[i];
443  if (!m_normalised) {
444  if (v > clip) {
445  buffer[i] = clip;
446  ++m_clippedCount;
447  } else if (v < -clip) {
448  buffer[i] = -clip;
449  ++m_clippedCount;
450  }
451  }
452  v = fabsf(v);
453  if (v != 0.f) {
454  if (m_firstNonzero == 0) {
456  }
458  if (v > m_max) {
459  m_max = v;
460  }
461  }
462  }
463  ++m_frameCount;
464  }
465 
466  if (m_max > 0.f) {
467  m_gain = 1.f / m_max; // used when normalising only
468  }
469 
470  switch (m_cacheMode) {
471 
473  if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
474  sf_close(m_cacheFileWritePtr);
475  m_cacheFileWritePtr = nullptr;
477  }
478  break;
479 
480  case CacheInMemory:
481  m_dataLock.lock();
482  try {
483  m_data.insert(m_data.end(), buffer, buffer + count);
484  } catch (const std::bad_alloc &e) {
485  m_data.clear();
486  SVCERR << "CodedAudioFileReader: Caught bad_alloc when trying to add " << count << " elements to buffer" << endl;
487  m_dataLock.unlock();
488  throw e;
489  }
490  m_dataLock.unlock();
491  break;
492  }
493 }
494 
495 void
497  double ratio, bool final)
498 {
499 // SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
500 
501  if (sz > 0) {
502 
503  sv_frame_t out = m_resampler->resampleInterleaved
506  buffer,
507  int(sz),
508  ratio,
509  false);
510 
512  }
513 
514  if (final) {
515 
516  sv_frame_t padFrames = 1;
517  if (double(m_frameCount) / ratio < double(m_fileFrameCount)) {
518  padFrames = m_fileFrameCount - sv_frame_t(double(m_frameCount) / ratio) + 1;
519  }
520 
521  sv_frame_t padSamples = padFrames * m_channelCount;
522 
523  SVDEBUG << "CodedAudioFileReader::pushBufferResampling: frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames = " << padFrames << ", padSamples = " << padSamples << endl;
524 
525  float *padding = new float[padSamples];
526  for (sv_frame_t i = 0; i < padSamples; ++i) padding[i] = 0.f;
527 
528  sv_frame_t out = m_resampler->resampleInterleaved
531  padding,
532  int(padFrames),
533  ratio,
534  true);
535 
536  SVDEBUG << "CodedAudioFileReader::pushBufferResampling: resampled padFrames to " << out << " frames" << endl;
537 
538  sv_frame_t expected = sv_frame_t(round(double(m_fileFrameCount) * ratio));
539  if (m_frameCount + out > expected) {
540  out = expected - m_frameCount;
541  SVDEBUG << "CodedAudioFileReader::pushBufferResampling: clipping that to " << out << " to avoid producing more samples than desired" << endl;
542  }
543 
545  delete[] padding;
546  }
547 }
548 
551 {
552  Profiler profiler("CodedAudioFileReader::getInterleavedFrames");
553 
554  // Lock is only required in CacheInMemory mode (the cache file
555  // reader is expected to be thread safe and manage its own
556  // locking)
557 
558  if (!m_initialised) {
559  SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
560  return {};
561  }
562 
563  floatvec_t frames;
564 
565  switch (m_cacheMode) {
566 
568  if (m_cacheFileReader) {
569  frames = m_cacheFileReader->getInterleavedFrames(start, count);
570  }
571  break;
572 
573  case CacheInMemory:
574  {
575  if (!isOK()) return {};
576  if (count == 0) return {};
577 
578  sv_frame_t ix0 = start * m_channelCount;
579  sv_frame_t ix1 = ix0 + (count * m_channelCount);
580 
581  // This lock used to be a QReadWriteLock, but it appears that
582  // its lock mechanism is significantly slower than QMutex so
583  // it's not a good idea in cases like this where we don't
584  // really have threads taking a long time to read concurrently
585  m_dataLock.lock();
586  sv_frame_t n = sv_frame_t(m_data.size());
587  if (ix0 > n) ix0 = n;
588  if (ix1 > n) ix1 = n;
589  frames = floatvec_t(m_data.begin() + ix0, m_data.begin() + ix1);
590  m_dataLock.unlock();
591  break;
592  }
593  }
594 
595  if (m_normalised) {
596  for (auto &f: frames) f *= m_gain;
597  }
598 
599  return frames;
600 }
601 
double sv_samplerate_t
Sample rate.
Definition: BaseTypes.h:51
sv_frame_t pushBuffer(float *interleaved, sv_frame_t sz, bool final)
int64_t sv_frame_t
Frame index, the unit of our time axis.
Definition: BaseTypes.h:31
Reader for audio files using libsndfile.
Definition: WavFileReader.h:41
void pushBufferResampling(float *interleaved, sv_frame_t sz, double ratio, bool final)
floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count) const override
Return interleaved samples for count frames from index start.
void startSerialised(QString id, const std::atomic< bool > *cancelled)
std::vector< float, breakfastquay::StlAllocator< float > > floatvec_t
Definition: BaseTypes.h:53
static TempDirectory * getInstance()
static void notifyPlannedAllocation(AllocationArea area, size_t size)
Specify that we are planning to use a given amount of storage (in kilobytes), but haven&#39;t allocated i...
void addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
QString getError() const override
If isOK() is false, return an error string.
Definition: WavFileReader.h:52
CodedAudioFileReader(CacheMode cacheMode, sv_samplerate_t targetRate, bool normalised)
WavFileReader * m_cacheFileReader
static void notifyDoneAllocation(AllocationArea area, size_t size)
Specify that we have now allocated, or abandoned the allocation of, the given amount (in kilobytes) o...
#define SVDEBUG
Definition: Debug.h:106
void pushBufferNonResampling(float *interleaved, sv_frame_t sz)
bool isOK() const
Return true if the file was opened successfully and no error has subsequently occurred.
void pushCacheWriteBufferMaybe(bool final)
void updateFrameCount()
breakfastquay::Resampler * m_resampler
void setFramesToTrim(sv_frame_t fromStart, sv_frame_t fromEnd)
#define SVCERR
Definition: Debug.h:109
sv_frame_t m_frameCount
floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count) const override
Must be safe to call from multiple threads with different arguments on the same object at the same ti...
sv_samplerate_t m_sampleRate
Profile point instance class.
Definition: Profiler.h:93