annotate data/fileio/WavFileReader.cpp @ 1496:fde8c497373f

Avoid crashing if an effects plugin can't be instantiated and so the output vector is empty in the transformer's run() method
author Chris Cannam
date Mon, 13 Aug 2018 15:25:32 +0100
parents 330bcc92507d
children 75d92155fa20
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@317 28 WavFileReader::WavFileReader(FileSource source, bool fileUpdating) :
Chris@1349 29 m_file(0),
Chris@316 30 m_source(source),
Chris@316 31 m_path(source.getLocalFilename()),
Chris@823 32 m_seekable(false),
Chris@148 33 m_lastStart(0),
Chris@176 34 m_lastCount(0),
Chris@176 35 m_updating(fileUpdating)
Chris@148 36 {
Chris@148 37 m_frameCount = 0;
Chris@148 38 m_channelCount = 0;
Chris@148 39 m_sampleRate = 0;
Chris@148 40
Chris@148 41 m_fileInfo.format = 0;
Chris@148 42 m_fileInfo.frames = 0;
Chris@148 43
Chris@1349 44 #ifdef Q_OS_WIN
Chris@1349 45 m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
Chris@1349 46 #else
Chris@1349 47 m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@1349 48 #endif
Chris@1349 49
Chris@1349 50 if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) {
Chris@1343 51 SVDEBUG << "WavFileReader::initialize: Failed to open file at \""
Chris@1349 52 << m_path << "\" ("
Chris@1349 53 << sf_strerror(m_file) << ")" << endl;
Chris@148 54
Chris@1349 55 if (m_file) {
Chris@1343 56 m_error = QString("Couldn't load audio file '%1':\n%2")
Chris@1349 57 .arg(m_path).arg(sf_strerror(m_file));
Chris@1343 58 } else {
Chris@1349 59 m_error = QString("Failed to open audio file '%1'")
Chris@1349 60 .arg(m_path);
Chris@1343 61 }
Chris@1343 62 return;
Chris@148 63 }
Chris@148 64
Chris@187 65 if (m_fileInfo.channels > 0) {
Chris@823 66
Chris@187 67 m_frameCount = m_fileInfo.frames;
Chris@187 68 m_channelCount = m_fileInfo.channels;
Chris@187 69 m_sampleRate = m_fileInfo.samplerate;
Chris@823 70
Chris@823 71 m_seekable = (m_fileInfo.seekable != 0);
Chris@823 72
Chris@823 73 int type = m_fileInfo.format & SF_FORMAT_TYPEMASK;
Chris@1162 74 int subtype = m_fileInfo.format & SF_FORMAT_SUBMASK;
Chris@1162 75
Chris@823 76 if (type >= SF_FORMAT_FLAC || type >= SF_FORMAT_OGG) {
Chris@1162 77 // Our m_seekable reports whether a file is rapidly
Chris@1162 78 // seekable, so things like Ogg don't qualify. We
Chris@1162 79 // cautiously report every file type of "at least" the
Chris@1162 80 // historical period of Ogg or FLAC as non-seekable.
Chris@823 81 m_seekable = false;
Chris@1162 82 } else if (type == SF_FORMAT_WAV && subtype <= SF_FORMAT_DOUBLE) {
Chris@1162 83 // libsndfile 1.0.26 has a bug (subsequently fixed in the
Chris@1162 84 // repo) that causes all files to be reported as
Chris@1162 85 // non-seekable. We know that certain common file types
Chris@1162 86 // are definitely seekable so, again cautiously, identify
Chris@1162 87 // and mark those (basically only non-adaptive WAVs).
Chris@1162 88 m_seekable = true;
Chris@823 89 }
Chris@187 90 }
Chris@175 91
Chris@1279 92 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 << endl;
Chris@148 93 }
Chris@148 94
Chris@148 95 WavFileReader::~WavFileReader()
Chris@148 96 {
Chris@1349 97 if (m_file) sf_close(m_file);
Chris@148 98 }
Chris@148 99
Chris@148 100 void
Chris@175 101 WavFileReader::updateFrameCount()
Chris@175 102 {
Chris@175 103 QMutexLocker locker(&m_mutex);
Chris@175 104
Chris@1038 105 sv_frame_t prevCount = m_fileInfo.frames;
Chris@175 106
Chris@1349 107 if (m_file) {
Chris@1349 108 sf_close(m_file);
Chris@1349 109 #ifdef Q_OS_WIN
Chris@1349 110 m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
Chris@1349 111 #else
Chris@1349 112 m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@1349 113 #endif
Chris@1349 114 if (!m_file || m_fileInfo.channels <= 0) {
Chris@1349 115 SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
Chris@1349 116 << sf_strerror(m_file) << ")" << endl;
Chris@175 117 }
Chris@175 118 }
Chris@175 119
Chris@690 120 // SVDEBUG << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << endl;
Chris@175 121
Chris@176 122 m_frameCount = m_fileInfo.frames;
Chris@176 123
Chris@187 124 if (m_channelCount == 0) {
Chris@187 125 m_channelCount = m_fileInfo.channels;
Chris@187 126 m_sampleRate = m_fileInfo.samplerate;
Chris@187 127 }
Chris@187 128
Chris@258 129 if (m_frameCount != prevCount) {
Chris@258 130 emit frameCountChanged();
Chris@258 131 }
Chris@176 132 }
Chris@176 133
Chris@176 134 void
Chris@176 135 WavFileReader::updateDone()
Chris@176 136 {
Chris@176 137 updateFrameCount();
Chris@176 138 m_updating = false;
Chris@175 139 }
Chris@175 140
Chris@1326 141 floatvec_t
Chris@1041 142 WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 143 {
Chris@1256 144 static HitCount lastRead("WavFileReader: last read");
Chris@1256 145
Chris@1096 146 if (count == 0) return {};
Chris@175 147
Chris@175 148 QMutexLocker locker(&m_mutex);
Chris@175 149
Chris@1258 150 Profiler profiler("WavFileReader::getInterleavedFrames");
Chris@1258 151
Chris@1349 152 if (!m_file || !m_channelCount) {
Chris@1096 153 return {};
Chris@175 154 }
Chris@148 155
Chris@1040 156 if (start >= m_fileInfo.frames) {
Chris@690 157 // SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
Chris@687 158 // << " > " << m_fileInfo.frames << endl;
Chris@1343 159 return {};
Chris@148 160 }
Chris@148 161
Chris@1040 162 if (start + count > m_fileInfo.frames) {
Chris@1343 163 count = m_fileInfo.frames - start;
Chris@148 164 }
Chris@148 165
Chris@1096 166 // Because WaveFileModel::getSummaries() is called separately for
Chris@1096 167 // individual channels, it's quite common for us to be called
Chris@1096 168 // repeatedly for the same data. So this is worth cacheing.
Chris@1096 169 if (start == m_lastStart && count == m_lastCount) {
Chris@1256 170 lastRead.hit();
Chris@1096 171 return m_buffer;
Chris@1096 172 }
Chris@1256 173
Chris@1256 174 // We don't actually support partial cache reads, but let's use
Chris@1256 175 // the term partial to refer to any forward seek and consider a
Chris@1256 176 // backward seek to be a miss
Chris@1256 177 if (start >= m_lastStart) {
Chris@1256 178 lastRead.partial();
Chris@1256 179 } else {
Chris@1256 180 lastRead.miss();
Chris@1256 181 }
Chris@1096 182
Chris@1349 183 if (sf_seek(m_file, start, SEEK_SET) < 0) {
Chris@1096 184 return {};
Chris@148 185 }
Chris@148 186
Chris@1326 187 floatvec_t data;
Chris@1096 188 sv_frame_t n = count * m_fileInfo.channels;
Chris@1096 189 data.resize(n);
Chris@1096 190
Chris@1096 191 m_lastStart = start;
Chris@1096 192 m_lastCount = count;
Chris@1096 193
Chris@1096 194 sf_count_t readCount = 0;
Chris@1349 195 if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
Chris@1096 196 return {};
Chris@1096 197 }
Chris@1096 198
Chris@1103 199 m_buffer = data;
Chris@1096 200 return data;
Chris@148 201 }
Chris@148 202
Chris@157 203 void
Chris@1096 204 WavFileReader::getSupportedExtensions(set<QString> &extensions)
Chris@157 205 {
Chris@157 206 int count;
Chris@157 207
Chris@157 208 if (sf_command(0, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) {
Chris@157 209 extensions.insert("wav");
Chris@157 210 extensions.insert("aiff");
Chris@316 211 extensions.insert("aifc");
Chris@157 212 extensions.insert("aif");
Chris@157 213 return;
Chris@157 214 }
Chris@157 215
Chris@157 216 SF_FORMAT_INFO info;
Chris@157 217 for (int i = 0; i < count; ++i) {
Chris@157 218 info.format = i;
Chris@157 219 if (!sf_command(0, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) {
Chris@783 220 QString ext = QString(info.extension).toLower();
Chris@783 221 extensions.insert(ext);
Chris@783 222 if (ext == "oga") {
Chris@783 223 // libsndfile is awfully proper, it says it only
Chris@783 224 // supports .oga but lots of Ogg audio files in the
Chris@783 225 // wild are .ogg and it will accept that
Chris@783 226 extensions.insert("ogg");
Chris@783 227 }
Chris@157 228 }
Chris@157 229 }
Chris@157 230 }
Chris@316 231
Chris@316 232 bool
Chris@316 233 WavFileReader::supportsExtension(QString extension)
Chris@316 234 {
Chris@1096 235 set<QString> extensions;
Chris@316 236 getSupportedExtensions(extensions);
Chris@316 237 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 238 }
Chris@316 239
Chris@316 240 bool
Chris@316 241 WavFileReader::supportsContentType(QString type)
Chris@316 242 {
Chris@316 243 return (type == "audio/x-wav" ||
Chris@316 244 type == "audio/x-aiff" ||
Chris@316 245 type == "audio/basic");
Chris@316 246 }
Chris@316 247
Chris@316 248 bool
Chris@317 249 WavFileReader::supports(FileSource &source)
Chris@316 250 {
Chris@316 251 return (supportsExtension(source.getExtension()) ||
Chris@316 252 supportsContentType(source.getContentType()));
Chris@316 253 }
Chris@316 254
Chris@316 255