annotate data/fileio/MP3FileReader.cpp @ 558:1d7ebc05157e

* Some fairly simplistic code to set up layer type properties based on RDF data about feature types (both when running transforms and when importing features from RDF files).
author Chris Cannam
date Thu, 12 Feb 2009 15:26:43 +0000
parents 3f68eab92857
children 0a9e960fca53
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@148 7 This file copyright 2006 Chris Cannam.
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 #ifdef HAVE_MAD
Chris@148 17
Chris@148 18 #include "MP3FileReader.h"
Chris@392 19 #include "base/ProgressReporter.h"
Chris@357 20
Chris@150 21 #include "system/System.h"
Chris@148 22
Chris@148 23 #include <sys/types.h>
Chris@148 24 #include <sys/stat.h>
Chris@148 25 #include <fcntl.h>
Chris@148 26
Chris@148 27 #include <iostream>
Chris@148 28
Chris@405 29 #include <cstdlib>
Chris@405 30
Chris@271 31 #ifdef HAVE_ID3TAG
Chris@271 32 #include <id3tag.h>
Chris@271 33 #endif
Chris@510 34 //#define DEBUG_ID3TAG 1
Chris@271 35
Chris@186 36 #include <QFileInfo>
Chris@148 37
Chris@317 38 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode,
Chris@392 39 CacheMode mode, size_t targetRate,
Chris@392 40 ProgressReporter *reporter) :
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@392 54 m_reporter = reporter;
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@392 111 if (m_reporter) {
Chris@392 112 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 113 m_reporter->setMessage
Chris@392 114 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 115 }
Chris@148 116
Chris@263 117 if (!decode(m_filebuffer, m_fileSize)) {
Chris@316 118 m_error = QString("Failed to decode file %1.").arg(m_path);
Chris@263 119 }
Chris@263 120
Chris@263 121 delete[] m_filebuffer;
Chris@263 122 m_filebuffer = 0;
Chris@148 123
Chris@263 124 if (isDecodeCacheInitialised()) finishDecodeCache();
Chris@398 125 endSerialised();
Chris@263 126
Chris@392 127 } else {
Chris@263 128
Chris@392 129 if (m_reporter) m_reporter->setProgress(100);
Chris@263 130
Chris@263 131 m_decodeThread = new DecodeThread(this);
Chris@263 132 m_decodeThread->start();
Chris@263 133
Chris@386 134 while ((m_channelCount == 0 || m_fileRate == 0 || m_sampleRate == 0)
Chris@386 135 && !m_done) {
Chris@263 136 usleep(10);
Chris@263 137 }
Chris@386 138
Chris@386 139 std::cerr << "MP3FileReader ctor: exiting with file rate = " << m_fileRate << std::endl;
Chris@148 140 }
Chris@499 141
Chris@499 142 if (m_error != "") {
Chris@499 143 std::cerr << "MP3FileReader::MP3FileReader(\"" << m_path.toStdString() << "\"): ERROR: " << m_error.toStdString() << std::endl;
Chris@499 144 }
Chris@148 145 }
Chris@148 146
Chris@148 147 MP3FileReader::~MP3FileReader()
Chris@148 148 {
Chris@263 149 if (m_decodeThread) {
Chris@265 150 m_cancelled = true;
Chris@263 151 m_decodeThread->wait();
Chris@263 152 delete m_decodeThread;
Chris@263 153 }
Chris@148 154 }
Chris@148 155
Chris@263 156 void
Chris@392 157 MP3FileReader::cancelled()
Chris@392 158 {
Chris@392 159 m_cancelled = true;
Chris@392 160 }
Chris@392 161
Chris@392 162 void
Chris@271 163 MP3FileReader::loadTags()
Chris@271 164 {
Chris@271 165 m_title = "";
Chris@271 166
Chris@271 167 #ifdef HAVE_ID3TAG
Chris@271 168
Chris@290 169 id3_file *file = id3_file_open(m_path.toLocal8Bit().data(),
Chris@271 170 ID3_FILE_MODE_READONLY);
Chris@271 171 if (!file) return;
Chris@271 172
Chris@273 173 // We can do this a lot more elegantly, but we'll leave that for
Chris@273 174 // when we implement support for more than just the one tag!
Chris@273 175
Chris@271 176 id3_tag *tag = id3_file_tag(file);
Chris@273 177 if (!tag) {
Chris@273 178 #ifdef DEBUG_ID3TAG
Chris@273 179 std::cerr << "MP3FileReader::loadTags: No ID3 tag found" << std::endl;
Chris@273 180 #endif
Chris@273 181 id3_file_close(file);
Chris@273 182 return;
Chris@271 183 }
Chris@271 184
Chris@333 185 m_title = loadTag(tag, "TIT2"); // work title
Chris@333 186 if (m_title == "") m_title = loadTag(tag, "TIT1");
Chris@273 187
Chris@333 188 m_maker = loadTag(tag, "TPE1"); // "lead artist"
Chris@333 189 if (m_maker == "") m_maker = loadTag(tag, "TPE2");
Chris@273 190
Chris@271 191 id3_file_close(file);
Chris@273 192
Chris@273 193 #else
Chris@273 194 #ifdef DEBUG_ID3TAG
Chris@273 195 std::cerr << "MP3FileReader::loadTags: ID3 tag support not compiled in"
Chris@273 196 << std::endl;
Chris@271 197 #endif
Chris@273 198 #endif
Chris@333 199 }
Chris@273 200
Chris@333 201 QString
Chris@333 202 MP3FileReader::loadTag(void *vtag, const char *name)
Chris@333 203 {
Chris@333 204 #ifdef HAVE_ID3TAG
Chris@333 205 id3_tag *tag = (id3_tag *)vtag;
Chris@333 206
Chris@333 207 id3_frame *frame = id3_tag_findframe(tag, name, 0);
Chris@333 208 if (!frame) {
Chris@333 209 #ifdef DEBUG_ID3TAG
Chris@333 210 std::cerr << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 211 #endif
Chris@333 212 return "";
Chris@333 213 }
Chris@333 214
Chris@333 215 if (frame->nfields < 2) {
Chris@333 216 std::cerr << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 217 return "";
Chris@333 218 }
Chris@333 219
Chris@333 220 unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]);
Chris@333 221 if (nstrings == 0) {
Chris@333 222 #ifdef DEBUG_ID3TAG
Chris@348 223 std::cerr << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 224 #endif
Chris@333 225 return "";
Chris@333 226 }
Chris@333 227
Chris@333 228 id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0);
Chris@333 229 if (!ustr) {
Chris@333 230 #ifdef DEBUG_ID3TAG
Chris@333 231 std::cerr << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << std::endl;
Chris@333 232 #endif
Chris@333 233 return "";
Chris@333 234 }
Chris@333 235
Chris@333 236 id3_utf8_t *u8str = id3_ucs4_utf8duplicate(ustr);
Chris@333 237 if (!u8str) {
Chris@333 238 std::cerr << "MP3FileReader::loadTags: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 title" << std::endl;
Chris@333 239 return "";
Chris@333 240 }
Chris@333 241
Chris@333 242 QString rv = QString::fromUtf8((const char *)u8str);
Chris@333 243 free(u8str);
Chris@334 244
Chris@334 245 #ifdef DEBUG_ID3TAG
Chris@334 246 std::cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
Chris@334 247 << rv.toStdString() << "\"" << std::endl;
Chris@334 248 #endif
Chris@334 249
Chris@334 250
Chris@333 251 return rv;
Chris@333 252
Chris@333 253 #else
Chris@333 254 return "";
Chris@333 255 #endif
Chris@271 256 }
Chris@271 257
Chris@271 258 void
Chris@263 259 MP3FileReader::DecodeThread::run()
Chris@263 260 {
Chris@263 261 if (!m_reader->decode(m_reader->m_filebuffer, m_reader->m_fileSize)) {
Chris@290 262 m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path);
Chris@263 263 }
Chris@263 264
Chris@263 265 delete[] m_reader->m_filebuffer;
Chris@263 266 m_reader->m_filebuffer = 0;
Chris@297 267
Chris@297 268 if (m_reader->m_samplebuffer) {
Chris@297 269 for (size_t c = 0; c < m_reader->m_channelCount; ++c) {
Chris@297 270 delete[] m_reader->m_samplebuffer[c];
Chris@297 271 }
Chris@297 272 delete[] m_reader->m_samplebuffer;
Chris@297 273 m_reader->m_samplebuffer = 0;
Chris@297 274 }
Chris@297 275
Chris@263 276 if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
Chris@263 277
Chris@263 278 m_reader->m_done = true;
Chris@265 279 m_reader->m_completion = 100;
Chris@297 280
Chris@297 281 m_reader->endSerialised();
Chris@263 282 }
Chris@263 283
Chris@148 284 bool
Chris@148 285 MP3FileReader::decode(void *mm, size_t sz)
Chris@148 286 {
Chris@148 287 DecoderData data;
Chris@148 288 struct mad_decoder decoder;
Chris@148 289
Chris@148 290 data.start = (unsigned char const *)mm;
Chris@148 291 data.length = (unsigned long)sz;
Chris@148 292 data.reader = this;
Chris@148 293
Chris@148 294 mad_decoder_init(&decoder, &data, input, 0, 0, output, error, 0);
Chris@148 295 mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
Chris@148 296 mad_decoder_finish(&decoder);
Chris@148 297
Chris@263 298 m_done = true;
Chris@148 299 return true;
Chris@148 300 }
Chris@148 301
Chris@148 302 enum mad_flow
Chris@148 303 MP3FileReader::input(void *dp, struct mad_stream *stream)
Chris@148 304 {
Chris@148 305 DecoderData *data = (DecoderData *)dp;
Chris@148 306
Chris@148 307 if (!data->length) return MAD_FLOW_STOP;
Chris@348 308
Chris@348 309 unsigned char const *start = data->start;
Chris@348 310 unsigned long length = data->length;
Chris@348 311
Chris@348 312 #ifdef HAVE_ID3TAG
Chris@348 313 if (length > ID3_TAG_QUERYSIZE) {
Chris@348 314 int taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE);
Chris@348 315 if (taglen > 0) {
Chris@348 316 // std::cerr << "ID3 tag length to skip: " << taglen << std::endl;
Chris@348 317 start += taglen;
Chris@348 318 length -= taglen;
Chris@348 319 }
Chris@348 320 }
Chris@348 321 #endif
Chris@348 322
Chris@348 323 mad_stream_buffer(stream, start, length);
Chris@148 324 data->length = 0;
Chris@148 325
Chris@148 326 return MAD_FLOW_CONTINUE;
Chris@148 327 }
Chris@148 328
Chris@148 329 enum mad_flow
Chris@148 330 MP3FileReader::output(void *dp,
Chris@148 331 struct mad_header const *header,
Chris@148 332 struct mad_pcm *pcm)
Chris@148 333 {
Chris@148 334 DecoderData *data = (DecoderData *)dp;
Chris@148 335 return data->reader->accept(header, pcm);
Chris@148 336 }
Chris@148 337
Chris@148 338 enum mad_flow
Chris@148 339 MP3FileReader::accept(struct mad_header const *header,
Chris@148 340 struct mad_pcm *pcm)
Chris@148 341 {
Chris@148 342 int channels = pcm->channels;
Chris@148 343 int frames = pcm->length;
Chris@148 344
Chris@148 345 if (header) {
Chris@148 346 m_bitrateNum += header->bitrate;
Chris@148 347 m_bitrateDenom ++;
Chris@148 348 }
Chris@148 349
Chris@148 350 if (frames < 1) return MAD_FLOW_CONTINUE;
Chris@148 351
Chris@148 352 if (m_channelCount == 0) {
Chris@297 353
Chris@297 354 m_fileRate = pcm->samplerate;
Chris@148 355 m_channelCount = channels;
Chris@297 356
Chris@297 357 initialiseDecodeCache();
Chris@297 358
Chris@297 359 if (m_cacheMode == CacheInTemporaryFile) {
Chris@522 360 // std::cerr << "MP3FileReader::accept: channel count " << m_channelCount << ", file rate " << m_fileRate << ", about to start serialised section" << std::endl;
Chris@297 361 startSerialised("MP3FileReader::Decode");
Chris@297 362 }
Chris@148 363 }
Chris@148 364
Chris@148 365 if (m_bitrateDenom > 0) {
Chris@148 366 double bitrate = m_bitrateNum / m_bitrateDenom;
Chris@148 367 double duration = double(m_fileSize * 8) / bitrate;
Chris@148 368 double elapsed = double(m_frameCount) / m_sampleRate;
Chris@375 369 double percent = 100;
Chris@375 370 if (duration > 0.0) percent = ((elapsed * 100.0) / duration);
Chris@357 371 int p = int(percent);
Chris@357 372 if (p < 1) p = 1;
Chris@357 373 if (p > 99) p = 99;
Chris@392 374 if (m_completion != p && m_reporter) {
Chris@357 375 m_completion = p;
Chris@392 376 m_reporter->setProgress(m_completion);
Chris@148 377 }
Chris@148 378 }
Chris@148 379
Chris@148 380 if (m_cancelled) return MAD_FLOW_STOP;
Chris@148 381
Chris@148 382 if (!isDecodeCacheInitialised()) {
Chris@148 383 initialiseDecodeCache();
Chris@148 384 }
Chris@148 385
Chris@297 386 if (m_samplebuffersize < frames) {
Chris@297 387 if (!m_samplebuffer) {
Chris@297 388 m_samplebuffer = new float *[channels];
Chris@297 389 for (int c = 0; c < channels; ++c) {
Chris@297 390 m_samplebuffer[c] = 0;
Chris@297 391 }
Chris@297 392 }
Chris@297 393 for (int c = 0; c < channels; ++c) {
Chris@297 394 delete[] m_samplebuffer[c];
Chris@297 395 m_samplebuffer[c] = new float[frames];
Chris@297 396 }
Chris@297 397 m_samplebuffersize = frames;
Chris@297 398 }
Chris@148 399
Chris@297 400 int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0]));
Chris@297 401
Chris@297 402 for (int ch = 0; ch < channels; ++ch) {
Chris@297 403
Chris@297 404 for (int i = 0; i < frames; ++i) {
Chris@297 405
Chris@148 406 mad_fixed_t sample = 0;
Chris@297 407 if (ch < activeChannels) {
Chris@148 408 sample = pcm->samples[ch][i];
Chris@148 409 }
Chris@148 410 float fsample = float(sample) / float(MAD_F_ONE);
Chris@297 411
Chris@297 412 m_samplebuffer[ch][i] = fsample;
Chris@148 413 }
Chris@148 414 }
Chris@148 415
Chris@297 416 addSamplesToDecodeCache(m_samplebuffer, frames);
Chris@148 417
Chris@148 418 return MAD_FLOW_CONTINUE;
Chris@148 419 }
Chris@148 420
Chris@148 421 enum mad_flow
Chris@148 422 MP3FileReader::error(void *dp,
Chris@148 423 struct mad_stream *stream,
Chris@148 424 struct mad_frame *)
Chris@148 425 {
Chris@148 426 DecoderData *data = (DecoderData *)dp;
Chris@148 427
Chris@523 428 // fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %lu\n",
Chris@523 429 // stream->error, mad_stream_errorstr(stream),
Chris@523 430 // (unsigned long)(stream->this_frame - data->start));
Chris@148 431
Chris@148 432 return MAD_FLOW_CONTINUE;
Chris@148 433 }
Chris@148 434
Chris@157 435 void
Chris@290 436 MP3FileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@157 437 {
Chris@157 438 extensions.insert("mp3");
Chris@157 439 }
Chris@157 440
Chris@316 441 bool
Chris@316 442 MP3FileReader::supportsExtension(QString extension)
Chris@316 443 {
Chris@316 444 std::set<QString> extensions;
Chris@316 445 getSupportedExtensions(extensions);
Chris@316 446 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 447 }
Chris@316 448
Chris@316 449 bool
Chris@316 450 MP3FileReader::supportsContentType(QString type)
Chris@316 451 {
Chris@316 452 return (type == "audio/mpeg");
Chris@316 453 }
Chris@316 454
Chris@316 455 bool
Chris@317 456 MP3FileReader::supports(FileSource &source)
Chris@316 457 {
Chris@316 458 return (supportsExtension(source.getExtension()) ||
Chris@316 459 supportsContentType(source.getContentType()));
Chris@316 460 }
Chris@316 461
Chris@316 462
Chris@148 463 #endif