annotate base/MatrixFileCache.cpp @ 92:4988de098b25

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