annotate data/fileio/MP3FileReader.cpp @ 458:f60360209e5c

* Fix race condition in FFTFileCache when reading from the same FFT model from multiple threads (e.g. when applying more than one plugin at once)
author Chris Cannam
date Wed, 15 Oct 2008 12:08:02 +0000
parents 65311fb86166
children b71116d3c180
rev   line source
Chris@148 1
Chris@148 2 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 3
Chris@148 4 /*
Chris@148 5 Sonic Visualiser
Chris@148 6 An audio file viewer and annotation editor.
Chris@148 7 Centre for Digital Music, Queen Mary, University of London.
Chris@148 8 This file copyright 2006 Chris Cannam.
Chris@148 9
Chris@148 10 This program is free software; you can redistribute it and/or
Chris@148 11 modify it under the terms of the GNU General Public License as
Chris@148 12 published by the Free Software Foundation; either version 2 of the
Chris@148 13 License, or (at your option) any later version. See the file
Chris@148 14 COPYING included with this distribution for more information.
Chris@148 15 */
Chris@148 16
Chris@148 17 #ifdef HAVE_MAD
Chris@148 18
Chris@148 19 #include "MP3FileReader.h"
Chris@392 20 #include "base/ProgressReporter.h"
Chris@357 21
Chris@150 22 #include "system/System.h"
Chris@148 23
Chris@148 24 #include <sys/types.h>
Chris@148 25 #include <sys/stat.h>
Chris@148 26 #include <fcntl.h>
Chris@148 27
Chris@148 28 #include <iostream>
Chris@148 29
Chris@405 30 #include <cstdlib>
Chris@405 31
Chris@271 32 #ifdef HAVE_ID3TAG
Chris@271 33 #include <id3tag.h>
Chris@271 34 #endif
Chris@273 35 #define DEBUG_ID3TAG 1
Chris@271 36
Chris@186 37 #include <QFileInfo>
Chris@148 38
Chris@317 39 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode,
Chris@392 40 CacheMode mode, size_t targetRate,
Chris@392 41 ProgressReporter *reporter) :
Chris@297 42 CodedAudioFileReader(mode, targetRate),
Chris@316 43 m_source(source),
Chris@316 44 m_path(source.getLocalFilename()),
Chris@264 45 m_decodeThread(0)
Chris@148 46 {
Chris@148 47 m_channelCount = 0;
Chris@297 48 m_fileRate = 0;
Chris@148 49 m_fileSize = 0;
Chris@148 50 m_bitrateNum = 0;
Chris@148 51 m_bitrateDenom = 0;
Chris@148 52 m_cancelled = false;
Chris@265 53 m_completion = 0;
Chris@263 54 m_done = false;
Chris@392 55 m_reporter = reporter;
Chris@148 56
Chris@148 57 struct stat stat;
Chris@316 58 if (::stat(m_path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) {
Chris@316 59 m_error = QString("File %1 does not exist.").arg(m_path);
Chris@148 60 return;
Chris@148 61 }
Chris@148 62
Chris@148 63 m_fileSize = stat.st_size;
Chris@148 64
Chris@229 65 int fd = -1;
Chris@316 66 if ((fd = ::open(m_path.toLocal8Bit().data(), O_RDONLY
Chris@231 67 #ifdef _WIN32
Chris@231 68 | O_BINARY
Chris@231 69 #endif
Chris@231 70 , 0)) < 0) {
Chris@316 71 m_error = QString("Failed to open file %1 for reading.").arg(m_path);
Chris@148 72 return;
Chris@148 73 }
Chris@148 74
Chris@263 75 m_filebuffer = 0;
Chris@297 76 m_samplebuffer = 0;
Chris@297 77 m_samplebuffersize = 0;
Chris@148 78
Chris@148 79 try {
Chris@263 80 m_filebuffer = new unsigned char[m_fileSize];
Chris@148 81 } catch (...) {
Chris@290 82 m_error = QString("Out of memory");
Chris@148 83 ::close(fd);
Chris@148 84 return;
Chris@148 85 }
Chris@148 86
Chris@229 87 ssize_t sz = 0;
Chris@229 88 size_t offset = 0;
Chris@229 89 while (offset < m_fileSize) {
Chris@263 90 sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset);
Chris@229 91 if (sz < 0) {
Chris@290 92 m_error = QString("Read error for file %1 (after %2 bytes)")
Chris@316 93 .arg(m_path).arg(offset);
Chris@263 94 delete[] m_filebuffer;
Chris@229 95 ::close(fd);
Chris@229 96 return;
Chris@230 97 } else if (sz == 0) {
Chris@231 98 std::cerr << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes")
Chris@231 99 .arg(offset).arg(m_fileSize).toStdString() << std::endl;
Chris@231 100 m_fileSize = offset;
Chris@230 101 break;
Chris@229 102 }
Chris@229 103 offset += sz;
Chris@148 104 }
Chris@148 105
Chris@148 106 ::close(fd);
Chris@148 107
Chris@271 108 loadTags();
Chris@271 109
Chris@263 110 if (decodeMode == DecodeAtOnce) {
Chris@263 111
Chris@392 112 if (m_reporter) {
Chris@392 113 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 114 m_reporter->setMessage
Chris@392 115 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 116 }
Chris@148 117
Chris@263 118 if (!decode(m_filebuffer, m_fileSize)) {
Chris@316 119 m_error = QString("Failed to decode file %1.").arg(m_path);
Chris@263 120 }
Chris@263 121
Chris@263 122 delete[] m_filebuffer;
Chris@263 123 m_filebuffer = 0;
Chris@148 124
Chris@263 125 if (isDecodeCacheInitialised()) finishDecodeCache();
Chris@398 126 endSerialised();
Chris@263 127
Chris@392 128 } else {
Chris@263 129
Chris@392 130 if (m_reporter) m_reporter->setProgress(100);
Chris@263 131
Chris@263 132 m_decodeThread = new DecodeThread(this);
Chris@263 133 m_decodeThread->start();
Chris@263 134
Chris@386 135 while ((m_channelCount == 0 || m_fileRate == 0 || m_sampleRate == 0)
Chris@386 136 && !m_done) {
Chris@263 137 usleep(10);
Chris@263 138 }
Chris@386 139
Chris@386 140 std::cerr << "MP3FileReader ctor: exiting with file rate = " << m_fileRate << std::endl;
Chris@148 141 }
Chris@148 142 }
Chris@148 143
Chris@148 144 MP3FileReader::~MP3FileReader()
Chris@148 145 {
Chris@263 146 if (m_decodeThread) {
Chris@265 147 m_cancelled = true;
Chris@263 148 m_decodeThread->wait();
Chris@263 149 delete m_decodeThread;
Chris@263 150 }
Chris@148 151 }
Chris@148 152
Chris@263 153 void
Chris@392 154 MP3FileReader::cancelled()
Chris@392 155 {
Chris@392 156 m_cancelled = true;
Chris@392 157 }
Chris@392 158
Chris@392 159 void
Chris@271 160 MP3FileReader::loadTags()
Chris@271 161 {
Chris@271 162 m_title = "";
Chris@271 163
Chris@271 164 #ifdef HAVE_ID3TAG
Chris@271 165
Chris@290 166 id3_file *file = id3_file_open(m_path.toLocal8Bit().data(),
Chris@271 167 ID3_FILE_MODE_READONLY);
Chris@271 168 if (!file) return;
Chris@271 169
Chris@273 170 // We can do this a lot more elegantly, but we'll leave that for
Chris@273 171 // when we implement support for more than just the one tag!
Chris@273 172
Chris@271 173 id3_tag *tag = id3_file_tag(file);
Chris@273 174 if (!tag) {
Chris@273 175 #ifdef DEBUG_ID3TAG
Chris@273 176 std::cerr << "MP3FileReader::loadTags: No ID3 tag found" << std::endl;
Chris@273 177 #endif
Chris@273 178 id3_file_close(file);
Chris@273 179 return;
Chris@271 180 }
Chris@271 181
Chris@333 182 m_title = loadTag(tag, "TIT2"); // work title
Chris@333 183 if (m_title == "") m_title = loadTag(tag, "TIT1");
Chris@273 184
Chris@333 185 m_maker = loadTag(tag, "TPE1"); // "lead artist"
Chris@333 186 if (m_maker == "") m_maker = loadTag(tag, "TPE2");
Chris@273 187
Chris@271 188 id3_file_close(file);
Chris@273 189
Chris@273 190 #else
Chris@273 191 #ifdef DEBUG_ID3TAG
Chris@273 192 std::cerr << "MP3FileReader::loadTags: ID3 tag support not compiled in"
Chris@273 193 << std::endl;
Chris@271 194 #endif
Chris@273 195 #endif
Chris@333 196 }
Chris@273 197
Chris@333 198 QString
Chris@333 199 MP3FileReader::loadTag(void *vtag, const char *name)
Chris@333 200 {
Chris@333 201 #ifdef HAVE_ID3TAG
Chris@333 202 id3_tag *tag = (id3_tag *)vtag;
Chris@333 203
Chris@333 204 id3_frame *frame = id3_tag_findframe(tag, name, 0);
Chris@333 205 if (!frame) {
Chris@333 206 #ifdef DEBUG_ID3TAG
Chris@333 207 std::cerr << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 208 #endif
Chris@333 209 return "";
Chris@333 210 }
Chris@333 211
Chris@333 212 if (frame->nfields < 2) {
Chris@333 213 std::cerr << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 214 return "";
Chris@333 215 }
Chris@333 216
Chris@333 217 unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]);
Chris@333 218 if (nstrings == 0) {
Chris@333 219 #ifdef DEBUG_ID3TAG
Chris@348 220 std::cerr << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 221 #endif
Chris@333 222 return "";
Chris@333 223 }
Chris@333 224
Chris@333 225 id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0);
Chris@333 226 if (!ustr) {
Chris@333 227 #ifdef DEBUG_ID3TAG
Chris@333 228 std::cerr << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 229 #endif
Chris@333 230 return "";
Chris@333 231 }
Chris@333 232
Chris@333 233 id3_utf8_t *u8str = id3_ucs4_utf8duplicate(ustr);
Chris@333 234 if (!u8str) {
Chris@333 235 std::cerr << "MP3FileReader::loadTags: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 title" << std::endl;
Chris@333 236 return "";
Chris@333 237 }
Chris@333 238
Chris@333 239 QString rv = QString::fromUtf8((const char *)u8str);
Chris@333 240 free(u8str);
Chris@334 241
Chris@334 242 #ifdef DEBUG_ID3TAG
Chris@334 243 std::cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
Chris@334 244 << rv.toStdString() << "\"" << std::endl;
Chris@334 245 #endif
Chris@334 246
Chris@334 247
Chris@333 248 return rv;
Chris@333 249
Chris@333 250 #else
Chris@333 251 return "";
Chris@333 252 #endif
Chris@271 253 }
Chris@271 254
Chris@271 255 void
Chris@263 256 MP3FileReader::DecodeThread::run()
Chris@263 257 {
Chris@263 258 if (!m_reader->decode(m_reader->m_filebuffer, m_reader->m_fileSize)) {
Chris@290 259 m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path);
Chris@263 260 }
Chris@263 261
Chris@263 262 delete[] m_reader->m_filebuffer;
Chris@263 263 m_reader->m_filebuffer = 0;
Chris@297 264
Chris@297 265 if (m_reader->m_samplebuffer) {
Chris@297 266 for (size_t c = 0; c < m_reader->m_channelCount; ++c) {
Chris@297 267 delete[] m_reader->m_samplebuffer[c];
Chris@297 268 }
Chris@297 269 delete[] m_reader->m_samplebuffer;
Chris@297 270 m_reader->m_samplebuffer = 0;
Chris@297 271 }
Chris@297 272
Chris@263 273 if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
Chris@263 274
Chris@263 275 m_reader->m_done = true;
Chris@265 276 m_reader->m_completion = 100;
Chris@297 277
Chris@297 278 m_reader->endSerialised();
Chris@263 279 }
Chris@263 280
Chris@148 281 bool
Chris@148 282 MP3FileReader::decode(void *mm, size_t sz)
Chris@148 283 {
Chris@148 284 DecoderData data;
Chris@148 285 struct mad_decoder decoder;
Chris@148 286
Chris@148 287 data.start = (unsigned char const *)mm;
Chris@148 288 data.length = (unsigned long)sz;
Chris@148 289 data.reader = this;
Chris@148 290
Chris@148 291 mad_decoder_init(&decoder, &data, input, 0, 0, output, error, 0);
Chris@148 292 mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
Chris@148 293 mad_decoder_finish(&decoder);
Chris@148 294
Chris@263 295 m_done = true;
Chris@148 296 return true;
Chris@148 297 }
Chris@148 298
Chris@148 299 enum mad_flow
Chris@148 300 MP3FileReader::input(void *dp, struct mad_stream *stream)
Chris@148 301 {
Chris@148 302 DecoderData *data = (DecoderData *)dp;
Chris@148 303
Chris@148 304 if (!data->length) return MAD_FLOW_STOP;
Chris@348 305
Chris@348 306 unsigned char const *start = data->start;
Chris@348 307 unsigned long length = data->length;
Chris@348 308
Chris@348 309 #ifdef HAVE_ID3TAG
Chris@348 310 if (length > ID3_TAG_QUERYSIZE) {
Chris@348 311 int taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE);
Chris@348 312 if (taglen > 0) {
Chris@348 313 // std::cerr << "ID3 tag length to skip: " << taglen << std::endl;
Chris@348 314 start += taglen;
Chris@348 315 length -= taglen;
Chris@348 316 }
Chris@348 317 }
Chris@348 318 #endif
Chris@348 319
Chris@348 320 mad_stream_buffer(stream, start, length);
Chris@148 321 data->length = 0;
Chris@148 322
Chris@148 323 return MAD_FLOW_CONTINUE;
Chris@148 324 }
Chris@148 325
Chris@148 326 enum mad_flow
Chris@148 327 MP3FileReader::output(void *dp,
Chris@148 328 struct mad_header const *header,
Chris@148 329 struct mad_pcm *pcm)
Chris@148 330 {
Chris@148 331 DecoderData *data = (DecoderData *)dp;
Chris@148 332 return data->reader->accept(header, pcm);
Chris@148 333 }
Chris@148 334
Chris@148 335 enum mad_flow
Chris@148 336 MP3FileReader::accept(struct mad_header const *header,
Chris@148 337 struct mad_pcm *pcm)
Chris@148 338 {
Chris@148 339 int channels = pcm->channels;
Chris@148 340 int frames = pcm->length;
Chris@148 341
Chris@148 342 if (header) {
Chris@148 343 m_bitrateNum += header->bitrate;
Chris@148 344 m_bitrateDenom ++;
Chris@148 345 }
Chris@148 346
Chris@148 347 if (frames < 1) return MAD_FLOW_CONTINUE;
Chris@148 348
Chris@148 349 if (m_channelCount == 0) {
Chris@297 350
Chris@297 351 m_fileRate = pcm->samplerate;
Chris@148 352 m_channelCount = channels;
Chris@297 353
Chris@297 354 initialiseDecodeCache();
Chris@297 355
Chris@297 356 if (m_cacheMode == CacheInTemporaryFile) {
Chris@357 357 // m_completion = 1;
Chris@297 358 std::cerr << "MP3FileReader::accept: channel count " << m_channelCount << ", file rate " << m_fileRate << ", about to start serialised section" << std::endl;
Chris@297 359 startSerialised("MP3FileReader::Decode");
Chris@297 360 }
Chris@148 361 }
Chris@148 362
Chris@148 363 if (m_bitrateDenom > 0) {
Chris@148 364 double bitrate = m_bitrateNum / m_bitrateDenom;
Chris@148 365 double duration = double(m_fileSize * 8) / bitrate;
Chris@148 366 double elapsed = double(m_frameCount) / m_sampleRate;
Chris@375 367 double percent = 100;
Chris@375 368 if (duration > 0.0) percent = ((elapsed * 100.0) / duration);
Chris@357 369 int p = int(percent);
Chris@357 370 if (p < 1) p = 1;
Chris@357 371 if (p > 99) p = 99;
Chris@392 372 if (m_completion != p && m_reporter) {
Chris@357 373 m_completion = p;
Chris@392 374 m_reporter->setProgress(m_completion);
Chris@148 375 }
Chris@148 376 }
Chris@148 377
Chris@148 378 if (m_cancelled) return MAD_FLOW_STOP;
Chris@148 379
Chris@148 380 if (!isDecodeCacheInitialised()) {
Chris@148 381 initialiseDecodeCache();
Chris@148 382 }
Chris@148 383
Chris@297 384 if (m_samplebuffersize < frames) {
Chris@297 385 if (!m_samplebuffer) {
Chris@297 386 m_samplebuffer = new float *[channels];
Chris@297 387 for (int c = 0; c < channels; ++c) {
Chris@297 388 m_samplebuffer[c] = 0;
Chris@297 389 }
Chris@297 390 }
Chris@297 391 for (int c = 0; c < channels; ++c) {
Chris@297 392 delete[] m_samplebuffer[c];
Chris@297 393 m_samplebuffer[c] = new float[frames];
Chris@297 394 }
Chris@297 395 m_samplebuffersize = frames;
Chris@297 396 }
Chris@148 397
Chris@297 398 int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0]));
Chris@297 399
Chris@297 400 for (int ch = 0; ch < channels; ++ch) {
Chris@297 401
Chris@297 402 for (int i = 0; i < frames; ++i) {
Chris@297 403
Chris@148 404 mad_fixed_t sample = 0;
Chris@297 405 if (ch < activeChannels) {
Chris@148 406 sample = pcm->samples[ch][i];
Chris@148 407 }
Chris@148 408 float fsample = float(sample) / float(MAD_F_ONE);
Chris@297 409
Chris@297 410 m_samplebuffer[ch][i] = fsample;
Chris@148 411 }
Chris@148 412 }
Chris@148 413
Chris@297 414 addSamplesToDecodeCache(m_samplebuffer, frames);
Chris@148 415
Chris@148 416 return MAD_FLOW_CONTINUE;
Chris@148 417 }
Chris@148 418
Chris@148 419 enum mad_flow
Chris@148 420 MP3FileReader::error(void *dp,
Chris@148 421 struct mad_stream *stream,
Chris@148 422 struct mad_frame *)
Chris@148 423 {
Chris@148 424 DecoderData *data = (DecoderData *)dp;
Chris@148 425
Chris@405 426 fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %lu\n",
Chris@148 427 stream->error, mad_stream_errorstr(stream),
Chris@405 428 (unsigned long)(stream->this_frame - data->start));
Chris@148 429
Chris@148 430 return MAD_FLOW_CONTINUE;
Chris@148 431 }
Chris@148 432
Chris@157 433 void
Chris@290 434 MP3FileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@157 435 {
Chris@157 436 extensions.insert("mp3");
Chris@157 437 }
Chris@157 438
Chris@316 439 bool
Chris@316 440 MP3FileReader::supportsExtension(QString extension)
Chris@316 441 {
Chris@316 442 std::set<QString> extensions;
Chris@316 443 getSupportedExtensions(extensions);
Chris@316 444 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 445 }
Chris@316 446
Chris@316 447 bool
Chris@316 448 MP3FileReader::supportsContentType(QString type)
Chris@316 449 {
Chris@316 450 return (type == "audio/mpeg");
Chris@316 451 }
Chris@316 452
Chris@316 453 bool
Chris@317 454 MP3FileReader::supports(FileSource &source)
Chris@316 455 {
Chris@316 456 return (supportsExtension(source.getExtension()) ||
Chris@316 457 supportsContentType(source.getContentType()));
Chris@316 458 }
Chris@316 459
Chris@316 460
Chris@148 461 #endif