annotate data/fileio/MP3FileReader.cpp @ 392:183ee2a55fc7

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