annotate data/fileio/WavFileReader.cpp @ 1588:0773b34d987f bqaudiostream

We should now be able to get these from Ogg files, though this might not be working yet...
author Chris Cannam
date Thu, 17 Jan 2019 15:08:38 +0000
parents 954d0cf29ca7
children 70e172e6cc59 f8e3dcbafb4d
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@1349 31 m_file(0),
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@187 98 }
Chris@175 99
Chris@1520 100 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 101 }
Chris@148 102
Chris@148 103 WavFileReader::~WavFileReader()
Chris@148 104 {
Chris@1349 105 if (m_file) sf_close(m_file);
Chris@148 106 }
Chris@148 107
Chris@148 108 void
Chris@175 109 WavFileReader::updateFrameCount()
Chris@175 110 {
Chris@175 111 QMutexLocker locker(&m_mutex);
Chris@175 112
Chris@1038 113 sv_frame_t prevCount = m_fileInfo.frames;
Chris@175 114
Chris@1349 115 if (m_file) {
Chris@1349 116 sf_close(m_file);
Chris@1349 117 #ifdef Q_OS_WIN
Chris@1349 118 m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
Chris@1349 119 #else
Chris@1349 120 m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@1349 121 #endif
Chris@1349 122 if (!m_file || m_fileInfo.channels <= 0) {
Chris@1349 123 SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
Chris@1349 124 << sf_strerror(m_file) << ")" << endl;
Chris@175 125 }
Chris@175 126 }
Chris@175 127
Chris@690 128 // SVDEBUG << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << endl;
Chris@175 129
Chris@176 130 m_frameCount = m_fileInfo.frames;
Chris@176 131
Chris@187 132 if (m_channelCount == 0) {
Chris@187 133 m_channelCount = m_fileInfo.channels;
Chris@187 134 m_sampleRate = m_fileInfo.samplerate;
Chris@187 135 }
Chris@187 136
Chris@258 137 if (m_frameCount != prevCount) {
Chris@258 138 emit frameCountChanged();
Chris@258 139 }
Chris@176 140 }
Chris@176 141
Chris@176 142 void
Chris@176 143 WavFileReader::updateDone()
Chris@176 144 {
Chris@176 145 updateFrameCount();
Chris@176 146 m_updating = false;
Chris@1520 147 if (m_normalisation != Normalisation::None) {
Chris@1513 148 m_max = getMax();
Chris@1513 149 }
Chris@175 150 }
Chris@175 151
Chris@1326 152 floatvec_t
Chris@1041 153 WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 154 {
Chris@1513 155 floatvec_t frames = getInterleavedFramesUnnormalised(start, count);
Chris@1513 156
Chris@1520 157 if (m_normalisation == Normalisation::None || m_max == 0.f) {
Chris@1513 158 return frames;
Chris@1513 159 }
Chris@1513 160
Chris@1513 161 for (int i = 0; in_range_for(frames, i); ++i) {
Chris@1513 162 frames[i] /= m_max;
Chris@1513 163 }
Chris@1513 164
Chris@1513 165 return frames;
Chris@1513 166 }
Chris@1513 167
Chris@1513 168 floatvec_t
Chris@1513 169 WavFileReader::getInterleavedFramesUnnormalised(sv_frame_t start,
Chris@1513 170 sv_frame_t count) const
Chris@1513 171 {
Chris@1256 172 static HitCount lastRead("WavFileReader: last read");
Chris@1256 173
Chris@1096 174 if (count == 0) return {};
Chris@175 175
Chris@175 176 QMutexLocker locker(&m_mutex);
Chris@175 177
Chris@1258 178 Profiler profiler("WavFileReader::getInterleavedFrames");
Chris@1258 179
Chris@1349 180 if (!m_file || !m_channelCount) {
Chris@1096 181 return {};
Chris@175 182 }
Chris@148 183
Chris@1040 184 if (start >= m_fileInfo.frames) {
Chris@690 185 // SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
Chris@687 186 // << " > " << m_fileInfo.frames << endl;
Chris@1343 187 return {};
Chris@148 188 }
Chris@148 189
Chris@1040 190 if (start + count > m_fileInfo.frames) {
Chris@1343 191 count = m_fileInfo.frames - start;
Chris@148 192 }
Chris@148 193
Chris@1096 194 // Because WaveFileModel::getSummaries() is called separately for
Chris@1096 195 // individual channels, it's quite common for us to be called
Chris@1096 196 // repeatedly for the same data. So this is worth cacheing.
Chris@1096 197 if (start == m_lastStart && count == m_lastCount) {
Chris@1256 198 lastRead.hit();
Chris@1096 199 return m_buffer;
Chris@1096 200 }
Chris@1256 201
Chris@1256 202 // We don't actually support partial cache reads, but let's use
Chris@1256 203 // the term partial to refer to any forward seek and consider a
Chris@1256 204 // backward seek to be a miss
Chris@1256 205 if (start >= m_lastStart) {
Chris@1256 206 lastRead.partial();
Chris@1256 207 } else {
Chris@1256 208 lastRead.miss();
Chris@1256 209 }
Chris@1096 210
Chris@1349 211 if (sf_seek(m_file, start, SEEK_SET) < 0) {
Chris@1096 212 return {};
Chris@148 213 }
Chris@148 214
Chris@1326 215 floatvec_t data;
Chris@1096 216 sv_frame_t n = count * m_fileInfo.channels;
Chris@1096 217 data.resize(n);
Chris@1096 218
Chris@1096 219 m_lastStart = start;
Chris@1096 220 m_lastCount = count;
Chris@1096 221
Chris@1096 222 sf_count_t readCount = 0;
Chris@1349 223 if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
Chris@1096 224 return {};
Chris@1096 225 }
Chris@1096 226
Chris@1103 227 m_buffer = data;
Chris@1096 228 return data;
Chris@148 229 }
Chris@148 230
Chris@1513 231 float
Chris@1513 232 WavFileReader::getMax() const
Chris@1513 233 {
Chris@1513 234 if (!m_file || !m_channelCount) {
Chris@1513 235 return 0.f;
Chris@1513 236 }
Chris@1513 237
Chris@1513 238 // First try for a PEAK chunk
Chris@1513 239
Chris@1513 240 double sfpeak = 0.0;
Chris@1513 241 if (sf_command(m_file, SFC_GET_SIGNAL_MAX, &sfpeak, sizeof(sfpeak))
Chris@1513 242 == SF_TRUE) {
Chris@1513 243 SVDEBUG << "File has a PEAK chunk reporting max level " << sfpeak
Chris@1513 244 << endl;
Chris@1513 245 return float(fabs(sfpeak));
Chris@1513 246 }
Chris@1513 247
Chris@1513 248 // Failing that, read all the samples
Chris@1513 249
Chris@1513 250 float peak = 0.f;
Chris@1513 251 sv_frame_t ix = 0, chunk = 65536;
Chris@1513 252
Chris@1513 253 while (ix < m_frameCount) {
Chris@1513 254 auto frames = getInterleavedFrames(ix, chunk);
Chris@1513 255 for (float x: frames) {
Chris@1513 256 float level = fabsf(x);
Chris@1513 257 if (level > peak) {
Chris@1513 258 peak = level;
Chris@1513 259 }
Chris@1513 260 }
Chris@1513 261 ix += chunk;
Chris@1513 262 }
Chris@1513 263
Chris@1513 264 SVDEBUG << "Measured file peak max level as " << peak << endl;
Chris@1513 265 return peak;
Chris@1513 266 }
Chris@1513 267
Chris@157 268 void
Chris@1096 269 WavFileReader::getSupportedExtensions(set<QString> &extensions)
Chris@157 270 {
Chris@157 271 int count;
Chris@157 272
Chris@157 273 if (sf_command(0, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) {
Chris@157 274 extensions.insert("wav");
Chris@157 275 extensions.insert("aiff");
Chris@316 276 extensions.insert("aifc");
Chris@157 277 extensions.insert("aif");
Chris@157 278 return;
Chris@157 279 }
Chris@157 280
Chris@157 281 SF_FORMAT_INFO info;
Chris@157 282 for (int i = 0; i < count; ++i) {
Chris@157 283 info.format = i;
Chris@157 284 if (!sf_command(0, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) {
Chris@783 285 QString ext = QString(info.extension).toLower();
Chris@783 286 extensions.insert(ext);
Chris@783 287 if (ext == "oga") {
Chris@783 288 // libsndfile is awfully proper, it says it only
Chris@783 289 // supports .oga but lots of Ogg audio files in the
Chris@783 290 // wild are .ogg and it will accept that
Chris@783 291 extensions.insert("ogg");
Chris@783 292 }
Chris@157 293 }
Chris@157 294 }
Chris@157 295 }
Chris@316 296
Chris@316 297 bool
Chris@316 298 WavFileReader::supportsExtension(QString extension)
Chris@316 299 {
Chris@1096 300 set<QString> extensions;
Chris@316 301 getSupportedExtensions(extensions);
Chris@316 302 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 303 }
Chris@316 304
Chris@316 305 bool
Chris@316 306 WavFileReader::supportsContentType(QString type)
Chris@316 307 {
Chris@316 308 return (type == "audio/x-wav" ||
Chris@316 309 type == "audio/x-aiff" ||
Chris@316 310 type == "audio/basic");
Chris@316 311 }
Chris@316 312
Chris@316 313 bool
Chris@317 314 WavFileReader::supports(FileSource &source)
Chris@316 315 {
Chris@316 316 return (supportsExtension(source.getExtension()) ||
Chris@316 317 supportsContentType(source.getContentType()));
Chris@316 318 }
Chris@316 319
Chris@316 320