annotate base/MatrixFileCache.cpp @ 90:c4e163f911dd

* Switch spectrogram layer over to using the new rudimentary disk-backed FFT cache
author Chris Cannam
date Wed, 03 May 2006 14:26:26 +0000
parents 7de62a884810
children 1dcf41ed3863
rev   line source
Chris@87 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@87 2
Chris@87 3 /*
Chris@87 4 Sonic Visualiser
Chris@87 5 An audio file viewer and annotation editor.
Chris@87 6 Centre for Digital Music, Queen Mary, University of London.
Chris@87 7 This file copyright 2006 Chris Cannam.
Chris@87 8
Chris@87 9 This program is free software; you can redistribute it and/or
Chris@87 10 modify it under the terms of the GNU General Public License as
Chris@87 11 published by the Free Software Foundation; either version 2 of the
Chris@87 12 License, or (at your option) any later version. See the file
Chris@87 13 COPYING included with this distribution for more information.
Chris@87 14 */
Chris@87 15
Chris@87 16 #include "MatrixFileCache.h"
Chris@87 17 #include "base/TempDirectory.h"
Chris@90 18 #include "base/System.h"
Chris@87 19
Chris@87 20 #include <sys/types.h>
Chris@87 21 #include <sys/stat.h>
Chris@87 22 #include <fcntl.h>
Chris@87 23 #include <unistd.h>
Chris@87 24
Chris@87 25 #include <iostream>
Chris@87 26
Chris@87 27 #include <cstdio>
Chris@87 28
Chris@87 29 #include <QFileInfo>
Chris@87 30 #include <QDir>
Chris@87 31
Chris@90 32 #define HAVE_MMAP 1
Chris@90 33
Chris@90 34 #ifdef HAVE_MMAP
Chris@90 35 #include <sys/mman.h>
Chris@90 36 #endif
Chris@90 37
Chris@90 38 //!!! This class is a work in progress -- it does only as much as we
Chris@90 39 // need for the current SpectrogramLayer. Slated for substantial
Chris@90 40 // refactoring and extension.
Chris@90 41
Chris@87 42 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
Chris@87 43 m_fd(-1),
Chris@87 44 m_mode(mode),
Chris@87 45 m_width(0),
Chris@87 46 m_height(0),
Chris@90 47 m_headerSize(2 * sizeof(size_t)),
Chris@90 48 m_autoRegionWidth(2048),
Chris@90 49 m_off(-1),
Chris@87 50 m_rx(0),
Chris@87 51 m_rw(0),
Chris@90 52 m_userRegion(false),
Chris@90 53 m_region(0),
Chris@90 54 m_mmapped(false),
Chris@90 55 m_mmapSize(0),
Chris@90 56 m_mmapOff(0),
Chris@90 57 m_preferMmap(true)
Chris@87 58 {
Chris@90 59 // Ensure header size is a multiple of the size of our data (for
Chris@90 60 // alignment purposes)
Chris@90 61 size_t hs = ((m_headerSize / sizeof(float)) * sizeof(float));
Chris@90 62 if (hs != m_headerSize) m_headerSize = hs + sizeof(float);
Chris@90 63
Chris@87 64 QDir tempDir(TempDirectory::instance()->getPath());
Chris@87 65 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@87 66 bool newFile = !QFileInfo(fileName).exists();
Chris@87 67
Chris@87 68 if (newFile && mode == ReadOnly) {
Chris@87 69 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: Read-only mode "
Chris@87 70 << "specified, but cache file does not exist" << std::endl;
Chris@87 71 return;
Chris@87 72 }
Chris@87 73
Chris@87 74 int flags = 0;
Chris@87 75 mode_t fmode = S_IRUSR | S_IWUSR;
Chris@87 76
Chris@87 77 if (mode == ReadWrite) {
Chris@87 78 flags = O_RDWR | O_CREAT;
Chris@87 79 } else {
Chris@87 80 flags = O_RDONLY;
Chris@87 81 }
Chris@87 82
Chris@90 83 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
Chris@87 84 ::perror("Open failed");
Chris@87 85 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
Chris@87 86 << "Failed to open cache file \""
Chris@87 87 << fileName.toStdString() << "\"";
Chris@87 88 if (mode == ReadWrite) std::cerr << " for writing";
Chris@87 89 std::cerr << std::endl;
Chris@87 90 }
Chris@87 91
Chris@87 92 if (newFile) {
Chris@87 93 resize(0, 0); // write header
Chris@87 94 } else {
Chris@87 95 size_t header[2];
Chris@90 96 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@87 97 perror("Read failed");
Chris@87 98 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
Chris@90 99 << "Failed to read header (fd " << m_fd << ", file \""
Chris@90 100 << fileName.toStdString() << "\")" << std::endl;
Chris@87 101 return;
Chris@87 102 }
Chris@87 103 m_width = header[0];
Chris@87 104 m_height = header[1];
Chris@87 105 seekTo(0, 0);
Chris@87 106 }
Chris@87 107
Chris@90 108 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@87 109
Chris@87 110 }
Chris@87 111
Chris@87 112 MatrixFileCache::~MatrixFileCache()
Chris@87 113 {
Chris@90 114 if (m_rw > 0) {
Chris@90 115 if (m_mmapped) {
Chris@90 116 #ifdef HAVE_MMAP
Chris@90 117 ::munmap(m_region, m_mmapSize);
Chris@90 118 #endif
Chris@90 119 } else {
Chris@90 120 delete[] m_region;
Chris@90 121 }
Chris@90 122 }
Chris@90 123
Chris@87 124 if (m_fd >= 0) {
Chris@87 125 if (::close(m_fd) < 0) {
Chris@87 126 ::perror("MatrixFileCache::~MatrixFileCache: close failed");
Chris@87 127 }
Chris@87 128 }
Chris@90 129
Chris@90 130 //!!! refcount and unlink
Chris@87 131 }
Chris@87 132
Chris@87 133 size_t
Chris@87 134 MatrixFileCache::getWidth() const
Chris@87 135 {
Chris@87 136 return m_width;
Chris@87 137 }
Chris@87 138
Chris@87 139 size_t
Chris@87 140 MatrixFileCache::getHeight() const
Chris@87 141 {
Chris@87 142 return m_height;
Chris@87 143 }
Chris@87 144
Chris@87 145 void
Chris@87 146 MatrixFileCache::resize(size_t w, size_t h)
Chris@87 147 {
Chris@87 148 if (m_mode != ReadWrite) {
Chris@87 149 std::cerr << "ERROR: MatrixFileCache::resize called on read-only cache"
Chris@87 150 << std::endl;
Chris@87 151 return;
Chris@87 152 }
Chris@87 153
Chris@87 154 off_t off = m_headerSize + (w * h * sizeof(float));
Chris@87 155
Chris@87 156 if (w * h > m_width * m_height) {
Chris@87 157
Chris@90 158 #ifdef HAVE_MMAP
Chris@90 159 // If we're going to mmap the file, we need to ensure it's long
Chris@90 160 // enough beforehand
Chris@90 161
Chris@90 162 if (m_preferMmap) {
Chris@90 163
Chris@90 164 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
Chris@90 165 ::perror("Seek failed");
Chris@90 166 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
Chris@90 167 << h << "): seek failed, cannot resize" << std::endl;
Chris@90 168 return;
Chris@90 169 }
Chris@90 170
Chris@90 171 // guess this requires efficient support for sparse files
Chris@90 172
Chris@90 173 float f(0);
Chris@90 174 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
Chris@90 175 ::perror("WARNING: MatrixFileCache::resize: write failed");
Chris@90 176 }
Chris@87 177 }
Chris@90 178 #endif
Chris@87 179
Chris@87 180 } else {
Chris@87 181
Chris@87 182 if (::ftruncate(m_fd, off) < 0) {
Chris@90 183 ::perror("WARNING: MatrixFileCache::resize: ftruncate failed");
Chris@87 184 }
Chris@87 185 }
Chris@87 186
Chris@87 187 m_width = 0;
Chris@87 188 m_height = 0;
Chris@87 189 m_off = 0;
Chris@87 190
Chris@87 191 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
Chris@87 192 ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed");
Chris@87 193 return;
Chris@87 194 }
Chris@87 195
Chris@87 196 size_t header[2];
Chris@87 197 header[0] = w;
Chris@87 198 header[1] = h;
Chris@87 199 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@87 200 ::perror("ERROR: MatrixFileCache::resize: Failed to write header");
Chris@87 201 return;
Chris@87 202 }
Chris@87 203
Chris@87 204 m_width = w;
Chris@87 205 m_height = h;
Chris@87 206
Chris@87 207 seekTo(0, 0);
Chris@87 208 }
Chris@87 209
Chris@87 210 void
Chris@87 211 MatrixFileCache::reset()
Chris@87 212 {
Chris@87 213 if (m_mode != ReadWrite) {
Chris@87 214 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache"
Chris@87 215 << std::endl;
Chris@87 216 return;
Chris@87 217 }
Chris@87 218
Chris@90 219 float *emptyCol = new float[m_height];
Chris@90 220 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
Chris@90 221
Chris@90 222 seekTo(0, 0);
Chris@90 223 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
Chris@90 224
Chris@90 225 delete[] emptyCol;
Chris@87 226 }
Chris@87 227
Chris@87 228 void
Chris@90 229 MatrixFileCache::setRegionOfInterest(size_t x, size_t width)
Chris@87 230 {
Chris@90 231 setRegion(x, width, true);
Chris@90 232 }
Chris@90 233
Chris@90 234 void
Chris@90 235 MatrixFileCache::clearRegionOfInterest()
Chris@90 236 {
Chris@90 237 m_userRegion = false;
Chris@87 238 }
Chris@87 239
Chris@87 240 float
Chris@87 241 MatrixFileCache::getValueAt(size_t x, size_t y) const
Chris@87 242 {
Chris@87 243 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
Chris@90 244 float *rp = getRegionPtr(x, y);
Chris@90 245 if (rp) return *rp;
Chris@90 246 } else if (!m_userRegion) {
Chris@90 247 if (autoSetRegion(x)) {
Chris@90 248 float *rp = getRegionPtr(x, y);
Chris@90 249 if (rp) return *rp;
Chris@90 250 }
Chris@87 251 }
Chris@87 252
Chris@87 253 if (!seekTo(x, y)) return 0.f;
Chris@87 254 float value;
Chris@90 255 ssize_t r = ::read(m_fd, &value, sizeof(float));
Chris@90 256 if (r != sizeof(float)) {
Chris@87 257 ::perror("MatrixFileCache::getValueAt: read failed");
Chris@90 258 value = 0.f;
Chris@87 259 }
Chris@90 260 if (r > 0) m_off += r;
Chris@87 261 return value;
Chris@87 262 }
Chris@87 263
Chris@87 264 void
Chris@87 265 MatrixFileCache::getColumnAt(size_t x, float *values) const
Chris@87 266 {
Chris@87 267 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
Chris@90 268 float *rp = getRegionPtr(x, 0);
Chris@90 269 if (rp) {
Chris@90 270 for (size_t y = 0; y < m_height; ++y) {
Chris@90 271 values[y] = rp[y];
Chris@90 272 }
Chris@90 273 return;
Chris@90 274 }
Chris@90 275 } else if (!m_userRegion) {
Chris@90 276 if (autoSetRegion(x)) {
Chris@90 277 float *rp = getRegionPtr(x, 0);
Chris@90 278 if (rp) {
Chris@90 279 for (size_t y = 0; y < m_height; ++y) {
Chris@90 280 values[y] = rp[y];
Chris@90 281 }
Chris@90 282 return;
Chris@90 283 }
Chris@87 284 }
Chris@87 285 }
Chris@87 286
Chris@87 287 if (!seekTo(x, 0)) return;
Chris@90 288 ssize_t r = ::read(m_fd, values, m_height * sizeof(float));
Chris@90 289 if (r != m_height * sizeof(float)) {
Chris@87 290 ::perror("MatrixFileCache::getColumnAt: read failed");
Chris@87 291 }
Chris@90 292 if (r > 0) m_off += r;
Chris@87 293 }
Chris@87 294
Chris@87 295 void
Chris@87 296 MatrixFileCache::setValueAt(size_t x, size_t y, float value)
Chris@87 297 {
Chris@87 298 if (m_mode != ReadWrite) {
Chris@87 299 std::cerr << "ERROR: MatrixFileCache::setValueAt called on read-only cache"
Chris@87 300 << std::endl;
Chris@87 301 return;
Chris@87 302 }
Chris@87 303
Chris@87 304 if (!seekTo(x, y)) return;
Chris@90 305 ssize_t w = ::write(m_fd, &value, sizeof(float));
Chris@90 306 if (w != sizeof(float)) {
Chris@87 307 ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
Chris@87 308 }
Chris@90 309 if (w > 0) m_off += w;
Chris@87 310
Chris@90 311 //... update region as appropriate
Chris@87 312 }
Chris@87 313
Chris@87 314 void
Chris@87 315 MatrixFileCache::setColumnAt(size_t x, float *values)
Chris@87 316 {
Chris@87 317 if (m_mode != ReadWrite) {
Chris@87 318 std::cerr << "ERROR: MatrixFileCache::setColumnAt called on read-only cache"
Chris@87 319 << std::endl;
Chris@87 320 return;
Chris@87 321 }
Chris@87 322
Chris@87 323 if (!seekTo(x, 0)) return;
Chris@90 324 ssize_t w = ::write(m_fd, values, m_height * sizeof(float));
Chris@90 325 if (w != m_height * sizeof(float)) {
Chris@87 326 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
Chris@87 327 }
Chris@90 328 if (w > 0) m_off += w;
Chris@87 329
Chris@90 330 //... update region as appropriate
Chris@90 331 }
Chris@90 332
Chris@90 333 float *
Chris@90 334 MatrixFileCache::getRegionPtr(size_t x, size_t y) const
Chris@90 335 {
Chris@90 336 if (m_rw == 0) return 0;
Chris@90 337
Chris@90 338 float *region = m_region;
Chris@90 339
Chris@90 340 if (m_mmapOff > 0) {
Chris@90 341 char *cr = (char *)m_region;
Chris@90 342 cr += m_mmapOff;
Chris@90 343 region = (float *)cr;
Chris@90 344 }
Chris@90 345
Chris@90 346 float *ptr = &(region[(x - m_rx) * m_height + y]);
Chris@90 347
Chris@90 348 // std::cerr << "getRegionPtr(" << x << "," << y << "): region is " << m_region << ", returning " << ptr << std::endl;
Chris@90 349 return ptr;
Chris@90 350 }
Chris@90 351
Chris@90 352 bool
Chris@90 353 MatrixFileCache::autoSetRegion(size_t x) const
Chris@90 354 {
Chris@90 355 size_t rx = x;
Chris@90 356 size_t rw = m_autoRegionWidth;
Chris@90 357 size_t left = rw / 4;
Chris@90 358 if (x < m_rx) left = (rw * 3) / 4;
Chris@90 359 if (rx > left) rx -= left;
Chris@90 360 else rx = 0;
Chris@90 361 if (rx + rw > m_width) rw = m_width - rx;
Chris@90 362 return setRegion(rx, rw, false);
Chris@90 363 }
Chris@90 364
Chris@90 365 bool
Chris@90 366 MatrixFileCache::setRegion(size_t x, size_t width, bool user) const
Chris@90 367 {
Chris@90 368 if (!user && m_userRegion) return false;
Chris@90 369 if (m_rw > 0 && x >= m_rx && x + width <= m_rx + m_rw) return true;
Chris@90 370
Chris@90 371 if (m_rw > 0) {
Chris@90 372 if (m_mmapped) {
Chris@90 373 #ifdef HAVE_MMAP
Chris@90 374 ::munmap(m_region, m_mmapSize);
Chris@90 375 std::cerr << "unmapped " << m_mmapSize << " at " << m_region << std::endl;
Chris@90 376 #endif
Chris@90 377 } else {
Chris@90 378 delete[] m_region;
Chris@90 379 }
Chris@90 380 m_region = 0;
Chris@90 381 m_mmapped = false;
Chris@90 382 m_mmapSize = 0;
Chris@90 383 m_mmapOff = 0;
Chris@90 384 m_rw = 0;
Chris@90 385 }
Chris@90 386
Chris@90 387 if (width == 0) {
Chris@90 388 return true;
Chris@90 389 }
Chris@90 390
Chris@90 391 #ifdef HAVE_MMAP
Chris@90 392
Chris@90 393 if (m_preferMmap) {
Chris@90 394
Chris@90 395 size_t mmapSize = m_height * width * sizeof(float);
Chris@90 396 off_t offset = m_headerSize + (x * m_height) * sizeof(float);
Chris@90 397 int pagesize = getpagesize();
Chris@90 398 off_t aligned = (offset / pagesize) * pagesize;
Chris@90 399 size_t mmapOff = offset - aligned;
Chris@90 400 mmapSize += mmapOff;
Chris@90 401
Chris@90 402 m_region = (float *)
Chris@90 403 ::mmap(0, mmapSize, PROT_READ, MAP_PRIVATE, m_fd, aligned);
Chris@90 404
Chris@90 405 if (m_region == MAP_FAILED) {
Chris@90 406
Chris@90 407 ::perror("Mmap failed");
Chris@90 408 std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", "
Chris@90 409 << width << "): Mmap(0, " << mmapSize
Chris@90 410 << ", " << PROT_READ << ", " << MAP_SHARED << ", " << m_fd
Chris@90 411 << ", " << aligned << ") failed, falling back to "
Chris@90 412 << "non-mmapping code for this cache" << std::endl;
Chris@90 413 m_preferMmap = false;
Chris@90 414
Chris@90 415 } else {
Chris@90 416
Chris@90 417 std::cerr << "mmap succeeded (offset " << aligned << ", size " << mmapSize << ", m_mmapOff " << mmapOff << ") = " << m_region << std::endl;
Chris@90 418
Chris@90 419 m_mmapped = true;
Chris@90 420 m_mmapSize = mmapSize;
Chris@90 421 m_mmapOff = mmapOff;
Chris@90 422 m_rx = x;
Chris@90 423 m_rw = width;
Chris@90 424 if (user) m_userRegion = true;
Chris@90 425 // MUNLOCK(m_region, m_mmapSize);
Chris@90 426 return true;
Chris@90 427 }
Chris@90 428 }
Chris@90 429 #endif
Chris@90 430
Chris@90 431 if (!seekTo(x, 0)) return false;
Chris@90 432
Chris@90 433 m_region = new float[width * m_height];
Chris@90 434
Chris@90 435 ssize_t r = ::read(m_fd, m_region, width * m_height * sizeof(float));
Chris@90 436 if (r < 0) {
Chris@90 437 ::perror("Read failed");
Chris@90 438 std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", " << width
Chris@90 439 << ") failed" << std::endl;
Chris@90 440 delete[] m_region;
Chris@90 441 m_region = 0;
Chris@90 442 return false;
Chris@90 443 }
Chris@90 444
Chris@90 445 m_off += r;
Chris@90 446
Chris@90 447 if (r < width * m_height * sizeof(float)) {
Chris@90 448 // didn't manage to read the whole thing, but did get something
Chris@90 449 std::cerr << "WARNING: MatrixFileCache::setRegion(" << x << ", " << width
Chris@90 450 << "): ";
Chris@90 451 width = r / (m_height * sizeof(float));
Chris@90 452 std::cerr << "Only got " << width << " columns" << std::endl;
Chris@90 453 }
Chris@90 454
Chris@90 455 m_rx = x;
Chris@90 456 m_rw = width;
Chris@90 457 if (m_rw == 0) {
Chris@90 458 delete[] m_region;
Chris@90 459 m_region = 0;
Chris@90 460 }
Chris@90 461
Chris@90 462 std::cerr << "MatrixFileCache::setRegion: set region to " << x << ", " << width << std::endl;
Chris@90 463
Chris@90 464 if (user) m_userRegion = true;
Chris@90 465 if (m_rw > 0) MUNLOCK(m_region, m_rw * m_height);
Chris@90 466 return true;
Chris@87 467 }
Chris@87 468
Chris@87 469 bool
Chris@87 470 MatrixFileCache::seekTo(size_t x, size_t y) const
Chris@87 471 {
Chris@87 472 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
Chris@87 473 if (off == m_off) return true;
Chris@87 474
Chris@90 475 if (m_mode == ReadWrite) {
Chris@90 476 std::cerr << "writer: ";
Chris@90 477 std::cerr << "seek required (from " << m_off << " to " << off << ")" << std::endl;
Chris@90 478 }
Chris@90 479
Chris@87 480 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@87 481 ::perror("Seek failed");
Chris@87 482 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y
Chris@87 483 << ") failed" << std::endl;
Chris@87 484 return false;
Chris@87 485 }
Chris@87 486
Chris@87 487 m_off = off;
Chris@87 488 return true;
Chris@87 489 }
Chris@87 490