annotate data/fileio/WavFileReader.cpp @ 1346:75ad55315db4 3.0-integration

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