annotate data/fileio/WavFileReader.cpp @ 1858:14747f24ad04

Use cancellable serialiser; add some more profiling points
author Chris Cannam
date Thu, 14 May 2020 16:36:48 +0100
parents ce185d4dd408
children
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@202 7 This file copyright 2006 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 "WavFileReader.h"
Chris@148 17
Chris@1256 18 #include "base/HitCount.h"
Chris@1258 19 #include "base/Profiler.h"
Chris@1256 20
Chris@148 21 #include <iostream>
Chris@148 22
Chris@175 23 #include <QMutexLocker>
Chris@316 24 #include <QFileInfo>
Chris@175 25
Chris@1096 26 using namespace std;
Chris@1096 27
Chris@1513 28 WavFileReader::WavFileReader(FileSource source,
Chris@1513 29 bool fileUpdating,
Chris@1520 30 Normalisation normalisation) :
Chris@1582 31 m_file(nullptr),
Chris@316 32 m_source(source),
Chris@316 33 m_path(source.getLocalFilename()),
Chris@823 34 m_seekable(false),
Chris@148 35 m_lastStart(0),
Chris@176 36 m_lastCount(0),
Chris@1520 37 m_normalisation(normalisation),
Chris@1513 38 m_max(0.f),
Chris@176 39 m_updating(fileUpdating)
Chris@148 40 {
Chris@148 41 m_frameCount = 0;
Chris@148 42 m_channelCount = 0;
Chris@148 43 m_sampleRate = 0;
Chris@148 44
Chris@148 45 m_fileInfo.format = 0;
Chris@148 46 m_fileInfo.frames = 0;
Chris@148 47
Chris@1349 48 #ifdef Q_OS_WIN
Chris@1349 49 m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
Chris@1349 50 #else
Chris@1349 51 m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@1349 52 #endif
Chris@1349 53
Chris@1349 54 if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) {
Chris@1343 55 SVDEBUG << "WavFileReader::initialize: Failed to open file at \""
Chris@1349 56 << m_path << "\" ("
Chris@1349 57 << sf_strerror(m_file) << ")" << endl;
Chris@148 58
Chris@1349 59 if (m_file) {
Chris@1343 60 m_error = QString("Couldn't load audio file '%1':\n%2")
Chris@1349 61 .arg(m_path).arg(sf_strerror(m_file));
Chris@1343 62 } else {
Chris@1349 63 m_error = QString("Failed to open audio file '%1'")
Chris@1349 64 .arg(m_path);
Chris@1343 65 }
Chris@1343 66 return;
Chris@148 67 }
Chris@148 68
Chris@187 69 if (m_fileInfo.channels > 0) {
Chris@823 70
Chris@187 71 m_frameCount = m_fileInfo.frames;
Chris@187 72 m_channelCount = m_fileInfo.channels;
Chris@187 73 m_sampleRate = m_fileInfo.samplerate;
Chris@823 74
Chris@823 75 m_seekable = (m_fileInfo.seekable != 0);
Chris@823 76
Chris@823 77 int type = m_fileInfo.format & SF_FORMAT_TYPEMASK;
Chris@1162 78 int subtype = m_fileInfo.format & SF_FORMAT_SUBMASK;
Chris@1162 79
Chris@823 80 if (type >= SF_FORMAT_FLAC || type >= SF_FORMAT_OGG) {
Chris@1162 81 // Our m_seekable reports whether a file is rapidly
Chris@1162 82 // seekable, so things like Ogg don't qualify. We
Chris@1162 83 // cautiously report every file type of "at least" the
Chris@1162 84 // historical period of Ogg or FLAC as non-seekable.
Chris@823 85 m_seekable = false;
Chris@1162 86 } else if (type == SF_FORMAT_WAV && subtype <= SF_FORMAT_DOUBLE) {
Chris@1162 87 // libsndfile 1.0.26 has a bug (subsequently fixed in the
Chris@1162 88 // repo) that causes all files to be reported as
Chris@1162 89 // non-seekable. We know that certain common file types
Chris@1162 90 // are definitely seekable so, again cautiously, identify
Chris@1162 91 // and mark those (basically only non-adaptive WAVs).
Chris@1162 92 m_seekable = true;
Chris@823 93 }
Chris@1513 94
Chris@1520 95 if (m_normalisation != Normalisation::None && !m_updating) {
Chris@1513 96 m_max = getMax();
Chris@1513 97 }
Chris@1592 98
Chris@1592 99 const char *str = sf_get_string(m_file, SF_STR_TITLE);
Chris@1592 100 if (str) {
Chris@1592 101 m_title = str;
Chris@1592 102 }
Chris@1592 103 str = sf_get_string(m_file, SF_STR_ARTIST);
Chris@1592 104 if (str) {
Chris@1592 105 m_maker = str;
Chris@1592 106 }
Chris@187 107 }
Chris@175 108
Chris@1520 109 SVDEBUG << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << ", normalisation " << int(m_normalisation) << endl;
Chris@148 110 }
Chris@148 111
Chris@148 112 WavFileReader::~WavFileReader()
Chris@148 113 {
Chris@1858 114 Profiler profiler("WavFileReader::~WavFileReader");
Chris@1858 115
Chris@1349 116 if (m_file) sf_close(m_file);
Chris@148 117 }
Chris@148 118
Chris@148 119 void
Chris@175 120 WavFileReader::updateFrameCount()
Chris@175 121 {
Chris@175 122 QMutexLocker locker(&m_mutex);
Chris@175 123
Chris@1038 124 sv_frame_t prevCount = m_fileInfo.frames;
Chris@175 125
Chris@1349 126 if (m_file) {
Chris@1349 127 sf_close(m_file);
Chris@1349 128 #ifdef Q_OS_WIN
Chris@1349 129 m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
Chris@1349 130 #else
Chris@1349 131 m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@1349 132 #endif
Chris@1349 133 if (!m_file || m_fileInfo.channels <= 0) {
Chris@1349 134 SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
Chris@1349 135 << sf_strerror(m_file) << ")" << endl;
Chris@175 136 }
Chris@175 137 }
Chris@175 138
Chris@690 139 // SVDEBUG << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << endl;
Chris@175 140
Chris@176 141 m_frameCount = m_fileInfo.frames;
Chris@176 142
Chris@187 143 if (m_channelCount == 0) {
Chris@187 144 m_channelCount = m_fileInfo.channels;
Chris@187 145 m_sampleRate = m_fileInfo.samplerate;
Chris@187 146 }
Chris@187 147
Chris@258 148 if (m_frameCount != prevCount) {
Chris@258 149 emit frameCountChanged();
Chris@258 150 }
Chris@176 151 }
Chris@176 152
Chris@176 153 void
Chris@176 154 WavFileReader::updateDone()
Chris@176 155 {
Chris@176 156 updateFrameCount();
Chris@176 157 m_updating = false;
Chris@1520 158 if (m_normalisation != Normalisation::None) {
Chris@1513 159 m_max = getMax();
Chris@1513 160 }
Chris@175 161 }
Chris@175 162
Chris@1326 163 floatvec_t
Chris@1041 164 WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 165 {
Chris@1513 166 floatvec_t frames = getInterleavedFramesUnnormalised(start, count);
Chris@1513 167
Chris@1520 168 if (m_normalisation == Normalisation::None || m_max == 0.f) {
Chris@1513 169 return frames;
Chris@1513 170 }
Chris@1513 171
Chris@1513 172 for (int i = 0; in_range_for(frames, i); ++i) {
Chris@1513 173 frames[i] /= m_max;
Chris@1513 174 }
Chris@1513 175
Chris@1513 176 return frames;
Chris@1513 177 }
Chris@1513 178
Chris@1513 179 floatvec_t
Chris@1513 180 WavFileReader::getInterleavedFramesUnnormalised(sv_frame_t start,
Chris@1513 181 sv_frame_t count) const
Chris@1513 182 {
Chris@1256 183 static HitCount lastRead("WavFileReader: last read");
Chris@1256 184
Chris@1096 185 if (count == 0) return {};
Chris@175 186
Chris@175 187 QMutexLocker locker(&m_mutex);
Chris@175 188
Chris@1258 189 Profiler profiler("WavFileReader::getInterleavedFrames");
Chris@1258 190
Chris@1349 191 if (!m_file || !m_channelCount) {
Chris@1096 192 return {};
Chris@175 193 }
Chris@148 194
Chris@1040 195 if (start >= m_fileInfo.frames) {
Chris@690 196 // SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
Chris@687 197 // << " > " << m_fileInfo.frames << endl;
Chris@1343 198 return {};
Chris@148 199 }
Chris@148 200
Chris@1040 201 if (start + count > m_fileInfo.frames) {
Chris@1343 202 count = m_fileInfo.frames - start;
Chris@148 203 }
Chris@148 204
Chris@1096 205 // Because WaveFileModel::getSummaries() is called separately for
Chris@1096 206 // individual channels, it's quite common for us to be called
Chris@1096 207 // repeatedly for the same data. So this is worth cacheing.
Chris@1096 208 if (start == m_lastStart && count == m_lastCount) {
Chris@1256 209 lastRead.hit();
Chris@1096 210 return m_buffer;
Chris@1096 211 }
Chris@1256 212
Chris@1256 213 // We don't actually support partial cache reads, but let's use
Chris@1256 214 // the term partial to refer to any forward seek and consider a
Chris@1256 215 // backward seek to be a miss
Chris@1256 216 if (start >= m_lastStart) {
Chris@1256 217 lastRead.partial();
Chris@1256 218 } else {
Chris@1256 219 lastRead.miss();
Chris@1256 220 }
Chris@1096 221
Chris@1349 222 if (sf_seek(m_file, start, SEEK_SET) < 0) {
Chris@1096 223 return {};
Chris@148 224 }
Chris@148 225
Chris@1326 226 floatvec_t data;
Chris@1096 227 sv_frame_t n = count * m_fileInfo.channels;
Chris@1096 228 data.resize(n);
Chris@1096 229
Chris@1096 230 m_lastStart = start;
Chris@1096 231 m_lastCount = count;
Chris@1096 232
Chris@1096 233 sf_count_t readCount = 0;
Chris@1349 234 if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
Chris@1096 235 return {};
Chris@1096 236 }
Chris@1096 237
Chris@1103 238 m_buffer = data;
Chris@1096 239 return data;
Chris@148 240 }
Chris@148 241
Chris@1513 242 float
Chris@1513 243 WavFileReader::getMax() const
Chris@1513 244 {
Chris@1513 245 if (!m_file || !m_channelCount) {
Chris@1513 246 return 0.f;
Chris@1513 247 }
Chris@1513 248
Chris@1513 249 // First try for a PEAK chunk
Chris@1513 250
Chris@1513 251 double sfpeak = 0.0;
Chris@1513 252 if (sf_command(m_file, SFC_GET_SIGNAL_MAX, &sfpeak, sizeof(sfpeak))
Chris@1513 253 == SF_TRUE) {
Chris@1513 254 SVDEBUG << "File has a PEAK chunk reporting max level " << sfpeak
Chris@1513 255 << endl;
Chris@1513 256 return float(fabs(sfpeak));
Chris@1513 257 }
Chris@1513 258
Chris@1513 259 // Failing that, read all the samples
Chris@1513 260
Chris@1513 261 float peak = 0.f;
Chris@1513 262 sv_frame_t ix = 0, chunk = 65536;
Chris@1513 263
Chris@1513 264 while (ix < m_frameCount) {
Chris@1513 265 auto frames = getInterleavedFrames(ix, chunk);
Chris@1513 266 for (float x: frames) {
Chris@1513 267 float level = fabsf(x);
Chris@1513 268 if (level > peak) {
Chris@1513 269 peak = level;
Chris@1513 270 }
Chris@1513 271 }
Chris@1513 272 ix += chunk;
Chris@1513 273 }
Chris@1513 274
Chris@1513 275 SVDEBUG << "Measured file peak max level as " << peak << endl;
Chris@1513 276 return peak;
Chris@1513 277 }
Chris@1513 278
Chris@157 279 void
Chris@1096 280 WavFileReader::getSupportedExtensions(set<QString> &extensions)
Chris@157 281 {
Chris@157 282 int count;
Chris@157 283
Chris@1582 284 if (sf_command(nullptr, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) {
Chris@157 285 extensions.insert("wav");
Chris@157 286 extensions.insert("aiff");
Chris@316 287 extensions.insert("aifc");
Chris@157 288 extensions.insert("aif");
Chris@157 289 return;
Chris@157 290 }
Chris@157 291
Chris@157 292 SF_FORMAT_INFO info;
Chris@157 293 for (int i = 0; i < count; ++i) {
Chris@157 294 info.format = i;
Chris@1582 295 if (!sf_command(nullptr, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) {
Chris@783 296 QString ext = QString(info.extension).toLower();
Chris@783 297 extensions.insert(ext);
Chris@783 298 if (ext == "oga") {
Chris@783 299 // libsndfile is awfully proper, it says it only
Chris@783 300 // supports .oga but lots of Ogg audio files in the
Chris@783 301 // wild are .ogg and it will accept that
Chris@783 302 extensions.insert("ogg");
Chris@783 303 }
Chris@157 304 }
Chris@157 305 }
Chris@157 306 }
Chris@316 307
Chris@316 308 bool
Chris@316 309 WavFileReader::supportsExtension(QString extension)
Chris@316 310 {
Chris@1096 311 set<QString> extensions;
Chris@316 312 getSupportedExtensions(extensions);
Chris@316 313 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 314 }
Chris@316 315
Chris@316 316 bool
Chris@316 317 WavFileReader::supportsContentType(QString type)
Chris@316 318 {
Chris@316 319 return (type == "audio/x-wav" ||
Chris@316 320 type == "audio/x-aiff" ||
Chris@316 321 type == "audio/basic");
Chris@316 322 }
Chris@316 323
Chris@316 324 bool
Chris@317 325 WavFileReader::supports(FileSource &source)
Chris@316 326 {
Chris@316 327 return (supportsExtension(source.getExtension()) ||
Chris@316 328 supportsContentType(source.getContentType()));
Chris@316 329 }
Chris@316 330
Chris@316 331