annotate data/fileio/MP3FileReader.cpp @ 1288:5ef9b4d4bbdb 3.0-integration

Filter out Xing/LAME info frames, rather than letting them go to the mp3 decoder as if they were audio frames. Fixes the 1152-sample zero pad at start of some decoded mp3 files (distinct from decoder delay). The logic here is based on the madplay code.
author Chris Cannam
date Thu, 24 Nov 2016 13:32:04 +0000
parents 97f21b03269b
children a45312bd9306
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@1168 34
Chris@186 35 #include <QFileInfo>
Chris@148 36
Chris@1218 37 #ifdef _MSC_VER
Chris@1218 38 #include <io.h>
Chris@1218 39 #define open _open
Chris@1218 40 #endif
Chris@1218 41
Chris@317 42 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode,
Chris@1040 43 CacheMode mode, sv_samplerate_t targetRate,
Chris@920 44 bool normalised,
Chris@392 45 ProgressReporter *reporter) :
Chris@920 46 CodedAudioFileReader(mode, targetRate, normalised),
Chris@316 47 m_source(source),
Chris@316 48 m_path(source.getLocalFilename()),
Chris@1284 49 m_decodeErrorShown(false),
Chris@264 50 m_decodeThread(0)
Chris@148 51 {
Chris@1279 52 SVDEBUG << "MP3FileReader: local path: \"" << m_path
Chris@1279 53 << "\", decode mode: " << decodeMode << " ("
Chris@1279 54 << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded")
Chris@1279 55 << ")" << endl;
Chris@1279 56
Chris@148 57 m_channelCount = 0;
Chris@297 58 m_fileRate = 0;
Chris@148 59 m_fileSize = 0;
Chris@148 60 m_bitrateNum = 0;
Chris@148 61 m_bitrateDenom = 0;
Chris@148 62 m_cancelled = false;
Chris@1288 63 m_mp3FrameCount = 0;
Chris@265 64 m_completion = 0;
Chris@263 65 m_done = false;
Chris@392 66 m_reporter = reporter;
Chris@148 67
Chris@148 68 struct stat stat;
Chris@316 69 if (::stat(m_path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) {
Chris@316 70 m_error = QString("File %1 does not exist.").arg(m_path);
Chris@148 71 return;
Chris@148 72 }
Chris@148 73
Chris@148 74 m_fileSize = stat.st_size;
Chris@148 75
Chris@977 76 m_filebuffer = 0;
Chris@977 77 m_samplebuffer = 0;
Chris@977 78 m_samplebuffersize = 0;
Chris@977 79
Chris@229 80 int fd = -1;
Chris@316 81 if ((fd = ::open(m_path.toLocal8Bit().data(), O_RDONLY
Chris@231 82 #ifdef _WIN32
Chris@231 83 | O_BINARY
Chris@231 84 #endif
Chris@231 85 , 0)) < 0) {
Chris@316 86 m_error = QString("Failed to open file %1 for reading.").arg(m_path);
Chris@148 87 return;
Chris@148 88 }
Chris@148 89
Chris@148 90 try {
Chris@263 91 m_filebuffer = new unsigned char[m_fileSize];
Chris@148 92 } catch (...) {
Chris@290 93 m_error = QString("Out of memory");
Chris@148 94 ::close(fd);
Chris@148 95 return;
Chris@148 96 }
Chris@148 97
Chris@229 98 ssize_t sz = 0;
Chris@1038 99 ssize_t offset = 0;
Chris@229 100 while (offset < m_fileSize) {
Chris@263 101 sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset);
Chris@229 102 if (sz < 0) {
Chris@290 103 m_error = QString("Read error for file %1 (after %2 bytes)")
Chris@316 104 .arg(m_path).arg(offset);
Chris@263 105 delete[] m_filebuffer;
Chris@229 106 ::close(fd);
Chris@229 107 return;
Chris@230 108 } else if (sz == 0) {
Chris@843 109 cerr << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes")
Chris@843 110 .arg(offset).arg(m_fileSize) << endl;
Chris@231 111 m_fileSize = offset;
Chris@230 112 break;
Chris@229 113 }
Chris@229 114 offset += sz;
Chris@148 115 }
Chris@148 116
Chris@148 117 ::close(fd);
Chris@148 118
Chris@271 119 loadTags();
Chris@271 120
Chris@263 121 if (decodeMode == DecodeAtOnce) {
Chris@263 122
Chris@392 123 if (m_reporter) {
Chris@392 124 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 125 m_reporter->setMessage
Chris@392 126 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 127 }
Chris@148 128
Chris@263 129 if (!decode(m_filebuffer, m_fileSize)) {
Chris@316 130 m_error = QString("Failed to decode file %1.").arg(m_path);
Chris@263 131 }
Chris@263 132
Chris@263 133 delete[] m_filebuffer;
Chris@263 134 m_filebuffer = 0;
Chris@148 135
Chris@263 136 if (isDecodeCacheInitialised()) finishDecodeCache();
Chris@398 137 endSerialised();
Chris@263 138
Chris@392 139 } else {
Chris@263 140
Chris@392 141 if (m_reporter) m_reporter->setProgress(100);
Chris@263 142
Chris@263 143 m_decodeThread = new DecodeThread(this);
Chris@263 144 m_decodeThread->start();
Chris@263 145
Chris@386 146 while ((m_channelCount == 0 || m_fileRate == 0 || m_sampleRate == 0)
Chris@386 147 && !m_done) {
Chris@263 148 usleep(10);
Chris@263 149 }
Chris@386 150
Chris@1288 151 SVDEBUG << "MP3FileReader: decoding startup complete, file rate = " << m_fileRate << endl;
Chris@148 152 }
Chris@499 153
Chris@499 154 if (m_error != "") {
Chris@1279 155 SVDEBUG << "MP3FileReader::MP3FileReader(\"" << m_path << "\"): ERROR: " << m_error << endl;
Chris@499 156 }
Chris@148 157 }
Chris@148 158
Chris@148 159 MP3FileReader::~MP3FileReader()
Chris@148 160 {
Chris@263 161 if (m_decodeThread) {
Chris@265 162 m_cancelled = true;
Chris@263 163 m_decodeThread->wait();
Chris@263 164 delete m_decodeThread;
Chris@263 165 }
Chris@148 166 }
Chris@148 167
Chris@263 168 void
Chris@392 169 MP3FileReader::cancelled()
Chris@392 170 {
Chris@392 171 m_cancelled = true;
Chris@392 172 }
Chris@392 173
Chris@392 174 void
Chris@271 175 MP3FileReader::loadTags()
Chris@271 176 {
Chris@271 177 m_title = "";
Chris@271 178
Chris@271 179 #ifdef HAVE_ID3TAG
Chris@271 180
Chris@290 181 id3_file *file = id3_file_open(m_path.toLocal8Bit().data(),
Chris@271 182 ID3_FILE_MODE_READONLY);
Chris@271 183 if (!file) return;
Chris@271 184
Chris@273 185 // We can do this a lot more elegantly, but we'll leave that for
Chris@273 186 // when we implement support for more than just the one tag!
Chris@273 187
Chris@271 188 id3_tag *tag = id3_file_tag(file);
Chris@273 189 if (!tag) {
Chris@1279 190 SVDEBUG << "MP3FileReader::loadTags: No ID3 tag found" << endl;
Chris@273 191 id3_file_close(file);
Chris@273 192 return;
Chris@271 193 }
Chris@271 194
Chris@333 195 m_title = loadTag(tag, "TIT2"); // work title
Chris@333 196 if (m_title == "") m_title = loadTag(tag, "TIT1");
Chris@1287 197 if (m_title == "") SVDEBUG << "MP3FileReader::loadTags: No title found" << endl;
Chris@273 198
Chris@333 199 m_maker = loadTag(tag, "TPE1"); // "lead artist"
Chris@333 200 if (m_maker == "") m_maker = loadTag(tag, "TPE2");
Chris@1287 201 if (m_maker == "") SVDEBUG << "MP3FileReader::loadTags: No artist/maker found" << endl;
Chris@273 202
Chris@632 203 for (unsigned int i = 0; i < tag->nframes; ++i) {
Chris@632 204 if (tag->frames[i]) {
Chris@632 205 QString value = loadTag(tag, tag->frames[i]->id);
Chris@1287 206 if (value != "") {
Chris@1287 207 m_tags[tag->frames[i]->id] = value;
Chris@1287 208 }
Chris@632 209 }
Chris@632 210 }
Chris@632 211
Chris@271 212 id3_file_close(file);
Chris@273 213
Chris@273 214 #else
Chris@1287 215 SVDEBUG << "MP3FileReader::loadTags: ID3 tag support not compiled in" << endl;
Chris@273 216 #endif
Chris@333 217 }
Chris@273 218
Chris@333 219 QString
Chris@333 220 MP3FileReader::loadTag(void *vtag, const char *name)
Chris@333 221 {
Chris@333 222 #ifdef HAVE_ID3TAG
Chris@333 223 id3_tag *tag = (id3_tag *)vtag;
Chris@333 224
Chris@333 225 id3_frame *frame = id3_tag_findframe(tag, name, 0);
Chris@333 226 if (!frame) {
Chris@1287 227 SVDEBUG << "MP3FileReader::loadTag: No \"" << name << "\" frame found in ID3 tag" << endl;
Chris@333 228 return "";
Chris@333 229 }
Chris@333 230
Chris@333 231 if (frame->nfields < 2) {
Chris@1287 232 cerr << "MP3FileReader::loadTag: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl;
Chris@333 233 return "";
Chris@333 234 }
Chris@333 235
Chris@333 236 unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]);
Chris@333 237 if (nstrings == 0) {
Chris@1287 238 SVDEBUG << "MP3FileReader::loadTag: No strings for \"" << name << "\" in ID3 tag" << endl;
Chris@333 239 return "";
Chris@333 240 }
Chris@333 241
Chris@333 242 id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0);
Chris@333 243 if (!ustr) {
Chris@1287 244 SVDEBUG << "MP3FileReader::loadTag: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl;
Chris@333 245 return "";
Chris@333 246 }
Chris@333 247
Chris@333 248 id3_utf8_t *u8str = id3_ucs4_utf8duplicate(ustr);
Chris@333 249 if (!u8str) {
Chris@1287 250 SVDEBUG << "MP3FileReader::loadTag: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 tag" << endl;
Chris@333 251 return "";
Chris@333 252 }
Chris@333 253
Chris@333 254 QString rv = QString::fromUtf8((const char *)u8str);
Chris@333 255 free(u8str);
Chris@334 256
Chris@1287 257 SVDEBUG << "MP3FileReader::loadTag: Tag \"" << name << "\" -> \""
Chris@1287 258 << rv << "\"" << endl;
Chris@334 259
Chris@333 260 return rv;
Chris@333 261
Chris@333 262 #else
Chris@333 263 return "";
Chris@333 264 #endif
Chris@271 265 }
Chris@271 266
Chris@271 267 void
Chris@263 268 MP3FileReader::DecodeThread::run()
Chris@263 269 {
Chris@263 270 if (!m_reader->decode(m_reader->m_filebuffer, m_reader->m_fileSize)) {
Chris@290 271 m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path);
Chris@263 272 }
Chris@263 273
Chris@263 274 delete[] m_reader->m_filebuffer;
Chris@263 275 m_reader->m_filebuffer = 0;
Chris@297 276
Chris@297 277 if (m_reader->m_samplebuffer) {
Chris@929 278 for (int c = 0; c < m_reader->m_channelCount; ++c) {
Chris@297 279 delete[] m_reader->m_samplebuffer[c];
Chris@297 280 }
Chris@297 281 delete[] m_reader->m_samplebuffer;
Chris@297 282 m_reader->m_samplebuffer = 0;
Chris@297 283 }
Chris@297 284
Chris@263 285 if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
Chris@263 286
Chris@263 287 m_reader->m_done = true;
Chris@265 288 m_reader->m_completion = 100;
Chris@297 289
Chris@297 290 m_reader->endSerialised();
Chris@263 291 }
Chris@263 292
Chris@148 293 bool
Chris@1038 294 MP3FileReader::decode(void *mm, sv_frame_t sz)
Chris@148 295 {
Chris@148 296 DecoderData data;
Chris@148 297 struct mad_decoder decoder;
Chris@148 298
Chris@148 299 data.start = (unsigned char const *)mm;
Chris@1288 300 data.length = sz;
Chris@148 301 data.reader = this;
Chris@148 302
Chris@1288 303 mad_decoder_init(&decoder, // decoder to initialise
Chris@1288 304 &data, // our own data block for callbacks
Chris@1288 305 input_callback, // provides (entire) input to mad
Chris@1288 306 0, // checks header
Chris@1288 307 filter_callback, // filters frame before decoding
Chris@1288 308 output_callback, // receives decoded output
Chris@1288 309 error_callback, // handles decode errors
Chris@1288 310 0); // "message_func"
Chris@1288 311
Chris@148 312 mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
Chris@148 313 mad_decoder_finish(&decoder);
Chris@148 314
Chris@1288 315 SVDEBUG << "MP3FileReader: Decoding complete, decoded " << m_mp3FrameCount
Chris@1288 316 << " mp3 frames" << endl;
Chris@1288 317
Chris@263 318 m_done = true;
Chris@148 319 return true;
Chris@148 320 }
Chris@148 321
Chris@148 322 enum mad_flow
Chris@1288 323 MP3FileReader::input_callback(void *dp, struct mad_stream *stream)
Chris@148 324 {
Chris@148 325 DecoderData *data = (DecoderData *)dp;
Chris@148 326
Chris@148 327 if (!data->length) return MAD_FLOW_STOP;
Chris@348 328
Chris@348 329 unsigned char const *start = data->start;
Chris@1288 330 sv_frame_t length = data->length;
Chris@348 331
Chris@348 332 #ifdef HAVE_ID3TAG
Chris@1288 333 while (length > ID3_TAG_QUERYSIZE) {
Chris@1038 334 ssize_t taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE);
Chris@1288 335 if (taglen <= 0) {
Chris@1288 336 break;
Chris@348 337 }
Chris@1288 338 SVDEBUG << "MP3FileReader: ID3 tag length to skip: " << taglen << endl;
Chris@1288 339 start += taglen;
Chris@1288 340 length -= taglen;
Chris@348 341 }
Chris@348 342 #endif
Chris@348 343
Chris@348 344 mad_stream_buffer(stream, start, length);
Chris@148 345 data->length = 0;
Chris@148 346
Chris@148 347 return MAD_FLOW_CONTINUE;
Chris@148 348 }
Chris@148 349
Chris@148 350 enum mad_flow
Chris@1288 351 MP3FileReader::filter_callback(void *dp,
Chris@1288 352 struct mad_stream const *stream,
Chris@1288 353 struct mad_frame *frame)
Chris@1288 354 {
Chris@1288 355 DecoderData *data = (DecoderData *)dp;
Chris@1288 356 return data->reader->filter(stream, frame);
Chris@1288 357 }
Chris@1288 358
Chris@1288 359 enum mad_flow
Chris@1288 360 MP3FileReader::filter(struct mad_stream const *stream,
Chris@1288 361 struct mad_frame *)
Chris@1288 362 {
Chris@1288 363 struct mad_bitptr ptr = stream->anc_ptr;
Chris@1288 364 unsigned long fourcc = mad_bit_read(&ptr, 32);
Chris@1288 365 std::string magic("....");
Chris@1288 366 for (int i = 0; i < 4; ++i) {
Chris@1288 367 magic[3-i] = char((fourcc >> (8*i)) & 0xff);
Chris@1288 368 }
Chris@1288 369 if (magic == "Xing" || magic == "Info" || magic == "LAME") {
Chris@1288 370 SVDEBUG << "MP3FileReader: Discarding metadata frame (magic = \""
Chris@1288 371 << magic << "\")" << " at frame " << m_mp3FrameCount << endl;
Chris@1288 372 return MAD_FLOW_IGNORE;
Chris@1288 373 } else {
Chris@1288 374 return MAD_FLOW_CONTINUE;
Chris@1288 375 }
Chris@1288 376 }
Chris@1288 377
Chris@1288 378 enum mad_flow
Chris@1288 379 MP3FileReader::output_callback(void *dp,
Chris@1288 380 struct mad_header const *header,
Chris@1288 381 struct mad_pcm *pcm)
Chris@148 382 {
Chris@148 383 DecoderData *data = (DecoderData *)dp;
Chris@148 384 return data->reader->accept(header, pcm);
Chris@148 385 }
Chris@148 386
Chris@148 387 enum mad_flow
Chris@148 388 MP3FileReader::accept(struct mad_header const *header,
Chris@148 389 struct mad_pcm *pcm)
Chris@148 390 {
Chris@148 391 int channels = pcm->channels;
Chris@148 392 int frames = pcm->length;
Chris@1288 393
Chris@148 394 if (header) {
Chris@1038 395 m_bitrateNum = m_bitrateNum + double(header->bitrate);
Chris@148 396 m_bitrateDenom ++;
Chris@148 397 }
Chris@148 398
Chris@148 399 if (frames < 1) return MAD_FLOW_CONTINUE;
Chris@148 400
Chris@148 401 if (m_channelCount == 0) {
Chris@297 402
Chris@297 403 m_fileRate = pcm->samplerate;
Chris@148 404 m_channelCount = channels;
Chris@297 405
Chris@297 406 initialiseDecodeCache();
Chris@297 407
Chris@297 408 if (m_cacheMode == CacheInTemporaryFile) {
Chris@690 409 // SVDEBUG << "MP3FileReader::accept: channel count " << m_channelCount << ", file rate " << m_fileRate << ", about to start serialised section" << endl;
Chris@297 410 startSerialised("MP3FileReader::Decode");
Chris@297 411 }
Chris@148 412 }
Chris@148 413
Chris@148 414 if (m_bitrateDenom > 0) {
Chris@148 415 double bitrate = m_bitrateNum / m_bitrateDenom;
Chris@148 416 double duration = double(m_fileSize * 8) / bitrate;
Chris@148 417 double elapsed = double(m_frameCount) / m_sampleRate;
Chris@375 418 double percent = 100;
Chris@375 419 if (duration > 0.0) percent = ((elapsed * 100.0) / duration);
Chris@357 420 int p = int(percent);
Chris@357 421 if (p < 1) p = 1;
Chris@357 422 if (p > 99) p = 99;
Chris@392 423 if (m_completion != p && m_reporter) {
Chris@357 424 m_completion = p;
Chris@392 425 m_reporter->setProgress(m_completion);
Chris@148 426 }
Chris@148 427 }
Chris@148 428
Chris@148 429 if (m_cancelled) return MAD_FLOW_STOP;
Chris@148 430
Chris@148 431 if (!isDecodeCacheInitialised()) {
Chris@148 432 initialiseDecodeCache();
Chris@148 433 }
Chris@148 434
Chris@929 435 if (int(m_samplebuffersize) < frames) {
Chris@297 436 if (!m_samplebuffer) {
Chris@297 437 m_samplebuffer = new float *[channels];
Chris@297 438 for (int c = 0; c < channels; ++c) {
Chris@297 439 m_samplebuffer[c] = 0;
Chris@297 440 }
Chris@297 441 }
Chris@297 442 for (int c = 0; c < channels; ++c) {
Chris@297 443 delete[] m_samplebuffer[c];
Chris@297 444 m_samplebuffer[c] = new float[frames];
Chris@297 445 }
Chris@297 446 m_samplebuffersize = frames;
Chris@297 447 }
Chris@148 448
Chris@297 449 int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0]));
Chris@297 450
Chris@297 451 for (int ch = 0; ch < channels; ++ch) {
Chris@297 452
Chris@297 453 for (int i = 0; i < frames; ++i) {
Chris@297 454
Chris@148 455 mad_fixed_t sample = 0;
Chris@297 456 if (ch < activeChannels) {
Chris@148 457 sample = pcm->samples[ch][i];
Chris@148 458 }
Chris@148 459 float fsample = float(sample) / float(MAD_F_ONE);
Chris@297 460
Chris@297 461 m_samplebuffer[ch][i] = fsample;
Chris@148 462 }
Chris@148 463 }
Chris@148 464
Chris@297 465 addSamplesToDecodeCache(m_samplebuffer, frames);
Chris@148 466
Chris@1288 467 ++m_mp3FrameCount;
Chris@1288 468
Chris@148 469 return MAD_FLOW_CONTINUE;
Chris@148 470 }
Chris@148 471
Chris@148 472 enum mad_flow
Chris@1288 473 MP3FileReader::error_callback(void *dp,
Chris@1288 474 struct mad_stream *stream,
Chris@1288 475 struct mad_frame *)
Chris@148 476 {
Chris@1284 477 DecoderData *data = (DecoderData *)dp;
Chris@1284 478 if (!data->reader->m_decodeErrorShown) {
Chris@1284 479 char buffer[256];
Chris@1284 480 snprintf(buffer, 255,
Chris@1284 481 "MP3 decoding error 0x%04x (%s) at byte offset %lu",
Chris@1284 482 stream->error, mad_stream_errorstr(stream),
Chris@1284 483 (unsigned long)(stream->this_frame - data->start));
Chris@1284 484 SVCERR << "Warning: in file \"" << data->reader->m_path << "\": "
Chris@1284 485 << buffer << " (continuing; will not report any further decode errors for this file)" << endl;
Chris@1284 486 data->reader->m_decodeErrorShown = true;
Chris@1279 487 }
Chris@148 488
Chris@148 489 return MAD_FLOW_CONTINUE;
Chris@148 490 }
Chris@148 491
Chris@157 492 void
Chris@290 493 MP3FileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@157 494 {
Chris@157 495 extensions.insert("mp3");
Chris@157 496 }
Chris@157 497
Chris@316 498 bool
Chris@316 499 MP3FileReader::supportsExtension(QString extension)
Chris@316 500 {
Chris@316 501 std::set<QString> extensions;
Chris@316 502 getSupportedExtensions(extensions);
Chris@316 503 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 504 }
Chris@316 505
Chris@316 506 bool
Chris@316 507 MP3FileReader::supportsContentType(QString type)
Chris@316 508 {
Chris@316 509 return (type == "audio/mpeg");
Chris@316 510 }
Chris@316 511
Chris@316 512 bool
Chris@317 513 MP3FileReader::supports(FileSource &source)
Chris@316 514 {
Chris@316 515 return (supportsExtension(source.getExtension()) ||
Chris@316 516 supportsContentType(source.getContentType()));
Chris@316 517 }
Chris@316 518
Chris@316 519
Chris@148 520 #endif