annotate data/fileio/MP3FileReader.cpp @ 360:ac300d385ab2

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